pytrilogy 0.0.3.93__py3-none-any.whl → 0.0.3.95__py3-none-any.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.
Potentially problematic release.
This version of pytrilogy might be problematic. Click here for more details.
- {pytrilogy-0.0.3.93.dist-info → pytrilogy-0.0.3.95.dist-info}/METADATA +170 -145
- {pytrilogy-0.0.3.93.dist-info → pytrilogy-0.0.3.95.dist-info}/RECORD +38 -34
- trilogy/__init__.py +1 -1
- trilogy/authoring/__init__.py +4 -0
- trilogy/core/enums.py +13 -0
- trilogy/core/env_processor.py +21 -10
- trilogy/core/environment_helpers.py +111 -0
- trilogy/core/exceptions.py +21 -1
- trilogy/core/functions.py +6 -1
- trilogy/core/graph_models.py +60 -67
- trilogy/core/internal.py +18 -0
- trilogy/core/models/author.py +16 -25
- trilogy/core/models/build.py +5 -4
- trilogy/core/models/core.py +3 -0
- trilogy/core/models/environment.py +28 -0
- trilogy/core/models/execute.py +7 -0
- trilogy/core/processing/node_generators/node_merge_node.py +30 -28
- trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +25 -11
- trilogy/core/processing/node_generators/select_merge_node.py +68 -82
- trilogy/core/query_processor.py +2 -1
- trilogy/core/statements/author.py +18 -3
- trilogy/core/statements/common.py +0 -10
- trilogy/core/statements/execute.py +71 -16
- trilogy/core/validation/__init__.py +0 -0
- trilogy/core/validation/common.py +109 -0
- trilogy/core/validation/concept.py +122 -0
- trilogy/core/validation/datasource.py +192 -0
- trilogy/core/validation/environment.py +71 -0
- trilogy/dialect/base.py +40 -21
- trilogy/dialect/sql_server.py +3 -1
- trilogy/engine.py +25 -7
- trilogy/executor.py +145 -83
- trilogy/parsing/parse_engine.py +35 -4
- trilogy/parsing/trilogy.lark +11 -5
- trilogy/core/processing/node_generators/select_merge_node_v2.py +0 -792
- {pytrilogy-0.0.3.93.dist-info → pytrilogy-0.0.3.95.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.3.93.dist-info → pytrilogy-0.0.3.95.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.3.93.dist-info → pytrilogy-0.0.3.95.dist-info}/licenses/LICENSE.md +0 -0
- {pytrilogy-0.0.3.93.dist-info → pytrilogy-0.0.3.95.dist-info}/top_level.txt +0 -0
trilogy/executor.py
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from functools import singledispatchmethod
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Any, Generator, List, Optional
|
|
4
|
+
from typing import Any, Generator, List, Optional
|
|
5
5
|
|
|
6
6
|
from sqlalchemy import text
|
|
7
|
-
from sqlalchemy.engine import CursorResult
|
|
8
7
|
|
|
9
8
|
from trilogy.constants import MagicConstants, Rendering, logger
|
|
10
|
-
from trilogy.core.enums import FunctionType, Granularity, IOType
|
|
9
|
+
from trilogy.core.enums import FunctionType, Granularity, IOType, ValidationScope
|
|
11
10
|
from trilogy.core.models.author import Concept, ConceptRef, Function
|
|
12
11
|
from trilogy.core.models.build import BuildFunction
|
|
13
12
|
from trilogy.core.models.core import ListWrapper, MapWrapper
|
|
@@ -23,44 +22,90 @@ from trilogy.core.statements.author import (
|
|
|
23
22
|
RawSQLStatement,
|
|
24
23
|
SelectStatement,
|
|
25
24
|
ShowStatement,
|
|
25
|
+
ValidateStatement,
|
|
26
26
|
)
|
|
27
27
|
from trilogy.core.statements.execute import (
|
|
28
|
+
PROCESSED_STATEMENT_TYPES,
|
|
28
29
|
ProcessedCopyStatement,
|
|
29
30
|
ProcessedQuery,
|
|
30
31
|
ProcessedQueryPersist,
|
|
31
32
|
ProcessedRawSQLStatement,
|
|
32
33
|
ProcessedShowStatement,
|
|
33
34
|
ProcessedStaticValueOutput,
|
|
35
|
+
ProcessedValidateStatement,
|
|
36
|
+
)
|
|
37
|
+
from trilogy.core.validation.common import (
|
|
38
|
+
ValidationTest,
|
|
34
39
|
)
|
|
35
40
|
from trilogy.dialect.base import BaseDialect
|
|
36
41
|
from trilogy.dialect.enums import Dialects
|
|
37
|
-
from trilogy.engine import ExecutionEngine
|
|
42
|
+
from trilogy.engine import ExecutionEngine, ResultProtocol
|
|
38
43
|
from trilogy.hooks.base_hook import BaseHook
|
|
39
44
|
from trilogy.parser import parse_text
|
|
40
45
|
from trilogy.render import get_dialect_generator
|
|
41
46
|
|
|
42
47
|
|
|
43
|
-
class ResultProtocol(Protocol):
|
|
44
|
-
values: List[Any]
|
|
45
|
-
columns: List[str]
|
|
46
|
-
|
|
47
|
-
def fetchall(self) -> List[Any]: ...
|
|
48
|
-
|
|
49
|
-
def keys(self) -> List[str]: ...
|
|
50
|
-
|
|
51
|
-
|
|
52
48
|
@dataclass
|
|
53
|
-
class MockResult:
|
|
49
|
+
class MockResult(ResultProtocol):
|
|
54
50
|
values: list[Any]
|
|
55
51
|
columns: list[str]
|
|
56
52
|
|
|
53
|
+
def __init__(self, values: list[Any], columns: list[str]):
|
|
54
|
+
processed = []
|
|
55
|
+
for x in values:
|
|
56
|
+
if isinstance(x, dict):
|
|
57
|
+
processed.append(MockResultRow(x))
|
|
58
|
+
else:
|
|
59
|
+
processed.append(x)
|
|
60
|
+
self.columns = columns
|
|
61
|
+
self.values = processed
|
|
62
|
+
|
|
63
|
+
def __iter__(self):
|
|
64
|
+
while self.values:
|
|
65
|
+
yield self.values.pop(0)
|
|
66
|
+
|
|
57
67
|
def fetchall(self):
|
|
58
68
|
return self.values
|
|
59
69
|
|
|
70
|
+
def fetchone(self):
|
|
71
|
+
if self.values:
|
|
72
|
+
return self.values.pop(0)
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
def fetchmany(self, size: int):
|
|
76
|
+
rval = self.values[:size]
|
|
77
|
+
self.values = self.values[size:]
|
|
78
|
+
return rval
|
|
79
|
+
|
|
60
80
|
def keys(self):
|
|
61
81
|
return self.columns
|
|
62
82
|
|
|
63
83
|
|
|
84
|
+
@dataclass
|
|
85
|
+
class MockResultRow:
|
|
86
|
+
_values: dict[str, Any]
|
|
87
|
+
|
|
88
|
+
def __str__(self) -> str:
|
|
89
|
+
return str(self._values)
|
|
90
|
+
|
|
91
|
+
def __repr__(self) -> str:
|
|
92
|
+
return repr(self._values)
|
|
93
|
+
|
|
94
|
+
def __getattr__(self, name: str) -> Any:
|
|
95
|
+
if name in self._values:
|
|
96
|
+
return self._values[name]
|
|
97
|
+
return super().__getattribute__(name)
|
|
98
|
+
|
|
99
|
+
def __getitem__(self, key: str) -> Any:
|
|
100
|
+
return self._values[key]
|
|
101
|
+
|
|
102
|
+
def values(self):
|
|
103
|
+
return self._values.values()
|
|
104
|
+
|
|
105
|
+
def keys(self):
|
|
106
|
+
return self._values.keys()
|
|
107
|
+
|
|
108
|
+
|
|
64
109
|
def generate_result_set(
|
|
65
110
|
columns: List[ConceptRef], output_data: list[Any]
|
|
66
111
|
) -> MockResult:
|
|
@@ -93,33 +138,18 @@ class Executor(object):
|
|
|
93
138
|
|
|
94
139
|
def execute_statement(
|
|
95
140
|
self,
|
|
96
|
-
statement:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
| ProcessedRawSQLStatement
|
|
100
|
-
| ProcessedQueryPersist
|
|
101
|
-
| ProcessedShowStatement
|
|
102
|
-
),
|
|
103
|
-
) -> Optional[CursorResult]:
|
|
104
|
-
if not isinstance(
|
|
105
|
-
statement,
|
|
106
|
-
(
|
|
107
|
-
ProcessedQuery,
|
|
108
|
-
ProcessedShowStatement,
|
|
109
|
-
ProcessedQueryPersist,
|
|
110
|
-
ProcessedCopyStatement,
|
|
111
|
-
ProcessedRawSQLStatement,
|
|
112
|
-
),
|
|
113
|
-
):
|
|
141
|
+
statement: PROCESSED_STATEMENT_TYPES,
|
|
142
|
+
) -> Optional[ResultProtocol]:
|
|
143
|
+
if not isinstance(statement, PROCESSED_STATEMENT_TYPES):
|
|
114
144
|
return None
|
|
115
145
|
return self.execute_query(statement)
|
|
116
146
|
|
|
117
147
|
@singledispatchmethod
|
|
118
|
-
def execute_query(self, query) ->
|
|
148
|
+
def execute_query(self, query) -> ResultProtocol | None:
|
|
119
149
|
raise NotImplementedError("Cannot execute type {}".format(type(query)))
|
|
120
150
|
|
|
121
151
|
@execute_query.register
|
|
122
|
-
def _(self, query: ConceptDeclarationStatement) ->
|
|
152
|
+
def _(self, query: ConceptDeclarationStatement) -> ResultProtocol | None:
|
|
123
153
|
concept = query.concept
|
|
124
154
|
return MockResult(
|
|
125
155
|
[
|
|
@@ -134,7 +164,7 @@ class Executor(object):
|
|
|
134
164
|
)
|
|
135
165
|
|
|
136
166
|
@execute_query.register
|
|
137
|
-
def _(self, query: Datasource) ->
|
|
167
|
+
def _(self, query: Datasource) -> ResultProtocol | None:
|
|
138
168
|
return MockResult(
|
|
139
169
|
[
|
|
140
170
|
{
|
|
@@ -145,39 +175,39 @@ class Executor(object):
|
|
|
145
175
|
)
|
|
146
176
|
|
|
147
177
|
@execute_query.register
|
|
148
|
-
def _(self, query: str) ->
|
|
178
|
+
def _(self, query: str) -> ResultProtocol | None:
|
|
149
179
|
results = self.execute_text(query)
|
|
150
180
|
if results:
|
|
151
181
|
return results[-1]
|
|
152
182
|
return None
|
|
153
183
|
|
|
154
184
|
@execute_query.register
|
|
155
|
-
def _(self, query: SelectStatement) ->
|
|
185
|
+
def _(self, query: SelectStatement) -> ResultProtocol | None:
|
|
156
186
|
sql = self.generator.generate_queries(
|
|
157
187
|
self.environment, [query], hooks=self.hooks
|
|
158
188
|
)
|
|
159
189
|
return self.execute_query(sql[0])
|
|
160
190
|
|
|
161
191
|
@execute_query.register
|
|
162
|
-
def _(self, query: PersistStatement) ->
|
|
192
|
+
def _(self, query: PersistStatement) -> ResultProtocol | None:
|
|
163
193
|
sql = self.generator.generate_queries(
|
|
164
194
|
self.environment, [query], hooks=self.hooks
|
|
165
195
|
)
|
|
166
196
|
return self.execute_query(sql[0])
|
|
167
197
|
|
|
168
198
|
@execute_query.register
|
|
169
|
-
def _(self, query: RawSQLStatement) ->
|
|
199
|
+
def _(self, query: RawSQLStatement) -> ResultProtocol | None:
|
|
170
200
|
return self.execute_raw_sql(query.text)
|
|
171
201
|
|
|
172
202
|
@execute_query.register
|
|
173
|
-
def _(self, query: ShowStatement) ->
|
|
203
|
+
def _(self, query: ShowStatement) -> ResultProtocol | None:
|
|
174
204
|
sql = self.generator.generate_queries(
|
|
175
205
|
self.environment, [query], hooks=self.hooks
|
|
176
206
|
)
|
|
177
207
|
return self.execute_query(sql[0])
|
|
178
208
|
|
|
179
209
|
@execute_query.register
|
|
180
|
-
def _(self, query: ProcessedShowStatement) ->
|
|
210
|
+
def _(self, query: ProcessedShowStatement) -> ResultProtocol | None:
|
|
181
211
|
return generate_result_set(
|
|
182
212
|
query.output_columns,
|
|
183
213
|
[
|
|
@@ -187,8 +217,31 @@ class Executor(object):
|
|
|
187
217
|
],
|
|
188
218
|
)
|
|
189
219
|
|
|
220
|
+
def _raw_validation_to_result(
|
|
221
|
+
self, raw: list[ValidationTest]
|
|
222
|
+
) -> Optional[ResultProtocol]:
|
|
223
|
+
if not raw:
|
|
224
|
+
return None
|
|
225
|
+
output = []
|
|
226
|
+
for row in raw:
|
|
227
|
+
output.append(
|
|
228
|
+
{
|
|
229
|
+
"check_type": row.check_type.value,
|
|
230
|
+
"expected": row.expected,
|
|
231
|
+
"result": str(row.result) if row.result else None,
|
|
232
|
+
"ran": row.ran,
|
|
233
|
+
"query": row.query if row.query else "",
|
|
234
|
+
}
|
|
235
|
+
)
|
|
236
|
+
return MockResult(output, ["check_type", "expected", "result", "ran", "query"])
|
|
237
|
+
|
|
190
238
|
@execute_query.register
|
|
191
|
-
def _(self, query:
|
|
239
|
+
def _(self, query: ProcessedValidateStatement) -> ResultProtocol | None:
|
|
240
|
+
results = self.validate_environment(query.scope, query.targets)
|
|
241
|
+
return self._raw_validation_to_result(results)
|
|
242
|
+
|
|
243
|
+
@execute_query.register
|
|
244
|
+
def _(self, query: ImportStatement) -> ResultProtocol | None:
|
|
192
245
|
return MockResult(
|
|
193
246
|
[
|
|
194
247
|
{
|
|
@@ -200,7 +253,7 @@ class Executor(object):
|
|
|
200
253
|
)
|
|
201
254
|
|
|
202
255
|
@execute_query.register
|
|
203
|
-
def _(self, query: MergeStatementV2) ->
|
|
256
|
+
def _(self, query: MergeStatementV2) -> ResultProtocol | None:
|
|
204
257
|
for concept in query.sources:
|
|
205
258
|
self.environment.merge_concept(
|
|
206
259
|
concept, query.targets[concept.address], modifiers=query.modifiers
|
|
@@ -217,17 +270,17 @@ class Executor(object):
|
|
|
217
270
|
)
|
|
218
271
|
|
|
219
272
|
@execute_query.register
|
|
220
|
-
def _(self, query: ProcessedRawSQLStatement) ->
|
|
273
|
+
def _(self, query: ProcessedRawSQLStatement) -> ResultProtocol | None:
|
|
221
274
|
return self.execute_raw_sql(query.text)
|
|
222
275
|
|
|
223
276
|
@execute_query.register
|
|
224
|
-
def _(self, query: ProcessedQuery) ->
|
|
277
|
+
def _(self, query: ProcessedQuery) -> ResultProtocol | None:
|
|
225
278
|
sql = self.generator.compile_statement(query)
|
|
226
279
|
output = self.execute_raw_sql(sql, local_concepts=query.local_concepts)
|
|
227
280
|
return output
|
|
228
281
|
|
|
229
282
|
@execute_query.register
|
|
230
|
-
def _(self, query: ProcessedQueryPersist) ->
|
|
283
|
+
def _(self, query: ProcessedQueryPersist) -> ResultProtocol | None:
|
|
231
284
|
sql = self.generator.compile_statement(query)
|
|
232
285
|
|
|
233
286
|
output = self.execute_raw_sql(sql, local_concepts=query.local_concepts)
|
|
@@ -235,9 +288,9 @@ class Executor(object):
|
|
|
235
288
|
return output
|
|
236
289
|
|
|
237
290
|
@execute_query.register
|
|
238
|
-
def _(self, query: ProcessedCopyStatement) ->
|
|
291
|
+
def _(self, query: ProcessedCopyStatement) -> ResultProtocol | None:
|
|
239
292
|
sql = self.generator.compile_statement(query)
|
|
240
|
-
output:
|
|
293
|
+
output: ResultProtocol = self.execute_raw_sql(
|
|
241
294
|
sql, local_concepts=query.local_concepts
|
|
242
295
|
)
|
|
243
296
|
if query.target_type == IOType.CSV:
|
|
@@ -278,8 +331,6 @@ class Executor(object):
|
|
|
278
331
|
for statement in sql:
|
|
279
332
|
compiled_sql = self.generator.compile_statement(statement)
|
|
280
333
|
output.append(compiled_sql)
|
|
281
|
-
|
|
282
|
-
output.append(compiled_sql)
|
|
283
334
|
return output
|
|
284
335
|
|
|
285
336
|
@generate_sql.register # type: ignore
|
|
@@ -313,23 +364,13 @@ class Executor(object):
|
|
|
313
364
|
|
|
314
365
|
def parse_file(
|
|
315
366
|
self, file: str | Path, persist: bool = False
|
|
316
|
-
) -> list[
|
|
317
|
-
ProcessedQuery
|
|
318
|
-
| ProcessedQueryPersist
|
|
319
|
-
| ProcessedShowStatement
|
|
320
|
-
| ProcessedRawSQLStatement
|
|
321
|
-
| ProcessedCopyStatement,
|
|
322
|
-
]:
|
|
367
|
+
) -> list[PROCESSED_STATEMENT_TYPES]:
|
|
323
368
|
return list(self.parse_file_generator(file, persist=persist))
|
|
324
369
|
|
|
325
370
|
def parse_file_generator(
|
|
326
371
|
self, file: str | Path, persist: bool = False
|
|
327
372
|
) -> Generator[
|
|
328
|
-
|
|
329
|
-
| ProcessedQueryPersist
|
|
330
|
-
| ProcessedShowStatement
|
|
331
|
-
| ProcessedRawSQLStatement
|
|
332
|
-
| ProcessedCopyStatement,
|
|
373
|
+
PROCESSED_STATEMENT_TYPES,
|
|
333
374
|
None,
|
|
334
375
|
None,
|
|
335
376
|
]:
|
|
@@ -340,23 +381,13 @@ class Executor(object):
|
|
|
340
381
|
|
|
341
382
|
def parse_text(
|
|
342
383
|
self, command: str, persist: bool = False, root: Path | None = None
|
|
343
|
-
) -> List[
|
|
344
|
-
ProcessedQuery
|
|
345
|
-
| ProcessedQueryPersist
|
|
346
|
-
| ProcessedShowStatement
|
|
347
|
-
| ProcessedRawSQLStatement
|
|
348
|
-
| ProcessedCopyStatement
|
|
349
|
-
]:
|
|
384
|
+
) -> List[PROCESSED_STATEMENT_TYPES]:
|
|
350
385
|
return list(self.parse_text_generator(command, persist=persist, root=root))
|
|
351
386
|
|
|
352
387
|
def parse_text_generator(
|
|
353
388
|
self, command: str, persist: bool = False, root: Path | None = None
|
|
354
389
|
) -> Generator[
|
|
355
|
-
|
|
356
|
-
| ProcessedQueryPersist
|
|
357
|
-
| ProcessedShowStatement
|
|
358
|
-
| ProcessedRawSQLStatement
|
|
359
|
-
| ProcessedCopyStatement,
|
|
390
|
+
PROCESSED_STATEMENT_TYPES,
|
|
360
391
|
None,
|
|
361
392
|
None,
|
|
362
393
|
]:
|
|
@@ -374,6 +405,7 @@ class Executor(object):
|
|
|
374
405
|
ShowStatement,
|
|
375
406
|
RawSQLStatement,
|
|
376
407
|
CopyStatement,
|
|
408
|
+
ValidateStatement,
|
|
377
409
|
),
|
|
378
410
|
)
|
|
379
411
|
]
|
|
@@ -420,10 +452,12 @@ class Executor(object):
|
|
|
420
452
|
# return self._concept_to_value(self.environment.concepts[rval.address], local_concepts=local_concepts)
|
|
421
453
|
return rval
|
|
422
454
|
else:
|
|
423
|
-
results = self.execute_query(f"select {concept.name} limit 1;")
|
|
424
|
-
|
|
455
|
+
results = self.execute_query(f"select {concept.name} limit 1;")
|
|
456
|
+
if results:
|
|
457
|
+
fetcher = results.fetchone()
|
|
458
|
+
if fetcher:
|
|
459
|
+
return fetcher[0]
|
|
425
460
|
return None
|
|
426
|
-
return results[0]
|
|
427
461
|
|
|
428
462
|
def _hydrate_param(
|
|
429
463
|
self, param: str, local_concepts: dict[str, Concept] | None = None
|
|
@@ -447,13 +481,16 @@ class Executor(object):
|
|
|
447
481
|
|
|
448
482
|
def execute_raw_sql(
|
|
449
483
|
self,
|
|
450
|
-
command: str,
|
|
484
|
+
command: str | Path,
|
|
451
485
|
variables: dict | None = None,
|
|
452
486
|
local_concepts: dict[str, Concept] | None = None,
|
|
453
|
-
) ->
|
|
487
|
+
) -> ResultProtocol:
|
|
454
488
|
"""Run a command against the raw underlying
|
|
455
489
|
execution engine."""
|
|
456
490
|
final_params = None
|
|
491
|
+
if isinstance(command, Path):
|
|
492
|
+
with open(command, "r") as f:
|
|
493
|
+
command = f.read()
|
|
457
494
|
q = text(command)
|
|
458
495
|
if variables:
|
|
459
496
|
final_params = variables
|
|
@@ -473,9 +510,9 @@ class Executor(object):
|
|
|
473
510
|
|
|
474
511
|
def execute_text(
|
|
475
512
|
self, command: str, non_interactive: bool = False
|
|
476
|
-
) -> List[
|
|
513
|
+
) -> List[ResultProtocol]:
|
|
477
514
|
"""Run a trilogy query expressed as text."""
|
|
478
|
-
output = []
|
|
515
|
+
output: list[ResultProtocol] = []
|
|
479
516
|
# connection = self.engine.connect()
|
|
480
517
|
for statement in self.parse_text_generator(command):
|
|
481
518
|
if isinstance(statement, ProcessedShowStatement):
|
|
@@ -491,19 +528,44 @@ class Executor(object):
|
|
|
491
528
|
[self.generator.compile_statement(x)],
|
|
492
529
|
)
|
|
493
530
|
)
|
|
531
|
+
elif isinstance(x, ProcessedValidateStatement):
|
|
532
|
+
raw = self.validate_environment(
|
|
533
|
+
x.scope, x.targets, generate_only=True
|
|
534
|
+
)
|
|
535
|
+
results = self._raw_validation_to_result(raw)
|
|
536
|
+
if results:
|
|
537
|
+
output.append(results)
|
|
538
|
+
else:
|
|
539
|
+
raise NotImplementedError(
|
|
540
|
+
f"Cannot show type {type(x)} in show statement"
|
|
541
|
+
)
|
|
494
542
|
continue
|
|
495
543
|
if non_interactive:
|
|
496
544
|
if not isinstance(
|
|
497
545
|
statement, (ProcessedCopyStatement, ProcessedQueryPersist)
|
|
498
546
|
):
|
|
499
547
|
continue
|
|
500
|
-
|
|
548
|
+
result = self.execute_statement(statement)
|
|
549
|
+
if result:
|
|
550
|
+
output.append(result)
|
|
501
551
|
return output
|
|
502
552
|
|
|
503
553
|
def execute_file(
|
|
504
554
|
self, file: str | Path, non_interactive: bool = False
|
|
505
|
-
) -> List[
|
|
555
|
+
) -> List[ResultProtocol]:
|
|
506
556
|
file = Path(file)
|
|
507
557
|
with open(file, "r") as f:
|
|
508
558
|
command = f.read()
|
|
509
559
|
return self.execute_text(command, non_interactive=non_interactive)
|
|
560
|
+
|
|
561
|
+
def validate_environment(
|
|
562
|
+
self,
|
|
563
|
+
scope: ValidationScope = ValidationScope.ALL,
|
|
564
|
+
targets: Optional[List[str]] = None,
|
|
565
|
+
generate_only: bool = False,
|
|
566
|
+
) -> list[ValidationTest]:
|
|
567
|
+
from trilogy.core.validation.environment import validate_environment
|
|
568
|
+
|
|
569
|
+
return validate_environment(
|
|
570
|
+
self.environment, self, scope, targets, generate_only
|
|
571
|
+
)
|
trilogy/parsing/parse_engine.py
CHANGED
|
@@ -38,13 +38,13 @@ from trilogy.core.enums import (
|
|
|
38
38
|
Ordering,
|
|
39
39
|
Purpose,
|
|
40
40
|
ShowCategory,
|
|
41
|
+
ValidationScope,
|
|
41
42
|
WindowOrder,
|
|
42
43
|
WindowType,
|
|
43
44
|
)
|
|
44
45
|
from trilogy.core.exceptions import InvalidSyntaxException, UndefinedConceptException
|
|
45
46
|
from trilogy.core.functions import (
|
|
46
47
|
CurrentDate,
|
|
47
|
-
CurrentDatetime,
|
|
48
48
|
FunctionFactory,
|
|
49
49
|
)
|
|
50
50
|
from trilogy.core.internal import ALL_ROWS_CONCEPT, INTERNAL_NAMESPACE
|
|
@@ -129,6 +129,7 @@ from trilogy.core.statements.author import (
|
|
|
129
129
|
SelectStatement,
|
|
130
130
|
ShowStatement,
|
|
131
131
|
TypeDeclaration,
|
|
132
|
+
ValidateStatement,
|
|
132
133
|
)
|
|
133
134
|
from trilogy.parsing.common import (
|
|
134
135
|
align_item_to_concept,
|
|
@@ -244,7 +245,7 @@ def unwrap_transformation(
|
|
|
244
245
|
elif isinstance(input, Parenthetical):
|
|
245
246
|
return unwrap_transformation(input.content, environment)
|
|
246
247
|
else:
|
|
247
|
-
return Function(
|
|
248
|
+
return Function.model_construct(
|
|
248
249
|
operator=FunctionType.CONSTANT,
|
|
249
250
|
output_datatype=arg_to_datatype(input),
|
|
250
251
|
output_purpose=Purpose.CONSTANT,
|
|
@@ -819,7 +820,9 @@ class ParseToObjects(Transformer):
|
|
|
819
820
|
|
|
820
821
|
@v_args(meta=True)
|
|
821
822
|
def aggregate_by(self, meta: Meta, args):
|
|
822
|
-
|
|
823
|
+
base = args[0]
|
|
824
|
+
b_concept = base.value.split(" ")[-1]
|
|
825
|
+
args = [self.environment.concepts[a] for a in [b_concept] + args[1:]]
|
|
823
826
|
return self.function_factory.create_function(args, FunctionType.GROUP, meta)
|
|
824
827
|
|
|
825
828
|
def whole_grain_clause(self, args) -> WholeGrainWrapper:
|
|
@@ -990,6 +993,25 @@ class ParseToObjects(Transformer):
|
|
|
990
993
|
def over_list(self, args):
|
|
991
994
|
return [x for x in args]
|
|
992
995
|
|
|
996
|
+
def VALIDATION_SCOPE(self, args) -> ValidationScope:
|
|
997
|
+
return ValidationScope(args.lower())
|
|
998
|
+
|
|
999
|
+
@v_args(meta=True)
|
|
1000
|
+
def validate_statement(self, meta: Meta, args) -> ValidateStatement:
|
|
1001
|
+
if len(args) == 2:
|
|
1002
|
+
scope = args[0]
|
|
1003
|
+
targets = args[1]
|
|
1004
|
+
elif len(args) == 0:
|
|
1005
|
+
scope = ValidationScope.ALL
|
|
1006
|
+
targets = None
|
|
1007
|
+
else:
|
|
1008
|
+
scope = args[0]
|
|
1009
|
+
targets = None
|
|
1010
|
+
return ValidateStatement(
|
|
1011
|
+
scope=scope,
|
|
1012
|
+
targets=targets,
|
|
1013
|
+
)
|
|
1014
|
+
|
|
993
1015
|
@v_args(meta=True)
|
|
994
1016
|
def merge_statement(self, meta: Meta, args) -> MergeStatementV2 | None:
|
|
995
1017
|
modifiers = []
|
|
@@ -2055,7 +2077,15 @@ class ParseToObjects(Transformer):
|
|
|
2055
2077
|
|
|
2056
2078
|
@v_args(meta=True)
|
|
2057
2079
|
def fcurrent_datetime(self, meta, args):
|
|
2058
|
-
return
|
|
2080
|
+
return self.function_factory.create_function(
|
|
2081
|
+
args=[], operator=FunctionType.CURRENT_DATETIME, meta=meta
|
|
2082
|
+
)
|
|
2083
|
+
|
|
2084
|
+
@v_args(meta=True)
|
|
2085
|
+
def fcurrent_timestamp(self, meta, args):
|
|
2086
|
+
return self.function_factory.create_function(
|
|
2087
|
+
args=[], operator=FunctionType.CURRENT_TIMESTAMP, meta=meta
|
|
2088
|
+
)
|
|
2059
2089
|
|
|
2060
2090
|
@v_args(meta=True)
|
|
2061
2091
|
def fnot(self, meta, args):
|
|
@@ -2232,6 +2262,7 @@ def parse_text(
|
|
|
2232
2262
|
| PersistStatement
|
|
2233
2263
|
| ShowStatement
|
|
2234
2264
|
| RawSQLStatement
|
|
2265
|
+
| ValidateStatement
|
|
2235
2266
|
| None
|
|
2236
2267
|
],
|
|
2237
2268
|
]:
|
trilogy/parsing/trilogy.lark
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
| copy_statement
|
|
13
13
|
| merge_statement
|
|
14
14
|
| rawsql_statement
|
|
15
|
+
| validate_statement
|
|
15
16
|
|
|
16
17
|
_TERMINATOR: ";"i /\s*/
|
|
17
18
|
|
|
@@ -81,8 +82,11 @@
|
|
|
81
82
|
// raw sql statement
|
|
82
83
|
rawsql_statement: "raw_sql"i "(" MULTILINE_STRING ")"
|
|
83
84
|
|
|
85
|
+
// validate_statement
|
|
84
86
|
|
|
87
|
+
VALIDATE_SCOPE: "concepts"i | "datasources"i
|
|
85
88
|
|
|
89
|
+
validate_statement: ("validate"i "all"i) | ( "validate"i VALIDATE_SCOPE (IDENTIFIER ("," IDENTIFIER)* ","? )? )
|
|
86
90
|
// copy statement
|
|
87
91
|
|
|
88
92
|
COPY_TYPE: "csv"i
|
|
@@ -262,10 +266,12 @@
|
|
|
262
266
|
//constant
|
|
263
267
|
CURRENT_DATE.1: /current_date\(\)/
|
|
264
268
|
CURRENT_DATETIME.1: /current_datetime\(\)/
|
|
269
|
+
CURRENT_TIMESTAMP.1: /current_timestamp\(\)/
|
|
265
270
|
fcurrent_date: CURRENT_DATE
|
|
266
271
|
fcurrent_datetime: CURRENT_DATETIME
|
|
272
|
+
fcurrent_timestamp: CURRENT_TIMESTAMP
|
|
267
273
|
|
|
268
|
-
_constant_functions: fcurrent_date | fcurrent_datetime
|
|
274
|
+
_constant_functions: fcurrent_date | fcurrent_datetime | fcurrent_timestamp
|
|
269
275
|
|
|
270
276
|
//string
|
|
271
277
|
_LIKE.1: "like("i
|
|
@@ -325,8 +331,8 @@
|
|
|
325
331
|
_GROUP.1: "group("i
|
|
326
332
|
fgroup: _GROUP expr ")" aggregate_over?
|
|
327
333
|
|
|
328
|
-
//by:
|
|
329
|
-
aggregate_by:
|
|
334
|
+
//by:
|
|
335
|
+
aggregate_by: /(group)\s+([a-zA-Z\_][a-zA-Z0-9\_\.]*)/i "BY"i (IDENTIFIER ",")* IDENTIFIER
|
|
330
336
|
|
|
331
337
|
//aggregates
|
|
332
338
|
_COUNT.1: "count("i
|
|
@@ -424,7 +430,7 @@
|
|
|
424
430
|
map_lit: "{" (literal ":" literal ",")* literal ":" literal ","? "}"
|
|
425
431
|
|
|
426
432
|
_STRUCT.1: "struct("i
|
|
427
|
-
struct_lit: _STRUCT (IDENTIFIER "
|
|
433
|
+
struct_lit: _STRUCT (IDENTIFIER "->" expr ",")* IDENTIFIER "->" expr ","? ")"
|
|
428
434
|
|
|
429
435
|
!bool_lit: "True"i | "False"i
|
|
430
436
|
|
|
@@ -455,7 +461,7 @@
|
|
|
455
461
|
DATASOURCES: "DATASOURCES"i
|
|
456
462
|
show_category: CONCEPTS | DATASOURCES
|
|
457
463
|
|
|
458
|
-
show_statement: "show"i ( show_category | select_statement | persist_statement) _TERMINATOR
|
|
464
|
+
show_statement: "show"i ( show_category | validate_statement | select_statement | persist_statement) _TERMINATOR
|
|
459
465
|
COMMENT: /#.*(\n|$)/ | /\/\/.*\n/
|
|
460
466
|
%import common.WS
|
|
461
467
|
%ignore WS
|