pytrilogy 0.0.3.94__py3-none-any.whl → 0.0.3.96__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.94.dist-info → pytrilogy-0.0.3.96.dist-info}/METADATA +184 -136
- {pytrilogy-0.0.3.94.dist-info → pytrilogy-0.0.3.96.dist-info}/RECORD +35 -30
- trilogy/__init__.py +1 -1
- trilogy/authoring/__init__.py +61 -43
- trilogy/core/enums.py +13 -0
- trilogy/core/env_processor.py +19 -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 +11 -37
- trilogy/core/internal.py +18 -0
- 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/select_merge_node.py +2 -2
- 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 +73 -16
- trilogy/core/validation/common.py +110 -0
- trilogy/core/validation/concept.py +125 -0
- trilogy/core/validation/datasource.py +194 -0
- trilogy/core/validation/environment.py +71 -0
- trilogy/dialect/base.py +48 -21
- trilogy/dialect/metadata.py +233 -0
- trilogy/dialect/sql_server.py +3 -1
- trilogy/engine.py +25 -7
- trilogy/executor.py +94 -162
- trilogy/parsing/parse_engine.py +34 -3
- trilogy/parsing/trilogy.lark +11 -5
- {pytrilogy-0.0.3.94.dist-info → pytrilogy-0.0.3.96.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.3.94.dist-info → pytrilogy-0.0.3.96.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.3.94.dist-info → pytrilogy-0.0.3.96.dist-info}/licenses/LICENSE.md +0 -0
- {pytrilogy-0.0.3.94.dist-info → pytrilogy-0.0.3.96.dist-info}/top_level.txt +0 -0
- /trilogy/{compiler.py → core/validation/__init__.py} +0 -0
trilogy/executor.py
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
1
|
from functools import singledispatchmethod
|
|
3
2
|
from pathlib import Path
|
|
4
|
-
from typing import Any, Generator, List, Optional
|
|
3
|
+
from typing import Any, Generator, List, Optional
|
|
5
4
|
|
|
6
5
|
from sqlalchemy import text
|
|
7
|
-
from sqlalchemy.engine import CursorResult
|
|
8
6
|
|
|
9
7
|
from trilogy.constants import MagicConstants, Rendering, logger
|
|
10
|
-
from trilogy.core.enums import FunctionType, Granularity, IOType
|
|
11
|
-
from trilogy.core.models.author import Concept,
|
|
8
|
+
from trilogy.core.enums import FunctionType, Granularity, IOType, ValidationScope
|
|
9
|
+
from trilogy.core.models.author import Concept, Function
|
|
12
10
|
from trilogy.core.models.build import BuildFunction
|
|
13
11
|
from trilogy.core.models.core import ListWrapper, MapWrapper
|
|
14
12
|
from trilogy.core.models.datasource import Datasource
|
|
@@ -23,53 +21,38 @@ from trilogy.core.statements.author import (
|
|
|
23
21
|
RawSQLStatement,
|
|
24
22
|
SelectStatement,
|
|
25
23
|
ShowStatement,
|
|
24
|
+
ValidateStatement,
|
|
26
25
|
)
|
|
27
26
|
from trilogy.core.statements.execute import (
|
|
27
|
+
PROCESSED_STATEMENT_TYPES,
|
|
28
28
|
ProcessedCopyStatement,
|
|
29
29
|
ProcessedQuery,
|
|
30
30
|
ProcessedQueryPersist,
|
|
31
31
|
ProcessedRawSQLStatement,
|
|
32
32
|
ProcessedShowStatement,
|
|
33
|
-
|
|
33
|
+
ProcessedValidateStatement,
|
|
34
|
+
)
|
|
35
|
+
from trilogy.core.validation.common import (
|
|
36
|
+
ValidationTest,
|
|
34
37
|
)
|
|
35
38
|
from trilogy.dialect.base import BaseDialect
|
|
36
39
|
from trilogy.dialect.enums import Dialects
|
|
37
|
-
from trilogy.
|
|
40
|
+
from trilogy.dialect.metadata import (
|
|
41
|
+
generate_result_set,
|
|
42
|
+
handle_concept_declaration,
|
|
43
|
+
handle_datasource,
|
|
44
|
+
handle_import_statement,
|
|
45
|
+
handle_merge_statement,
|
|
46
|
+
handle_processed_show_statement,
|
|
47
|
+
handle_processed_validate_statement,
|
|
48
|
+
handle_show_statement_outputs,
|
|
49
|
+
)
|
|
50
|
+
from trilogy.engine import ExecutionEngine, ResultProtocol
|
|
38
51
|
from trilogy.hooks.base_hook import BaseHook
|
|
39
52
|
from trilogy.parser import parse_text
|
|
40
53
|
from trilogy.render import get_dialect_generator
|
|
41
54
|
|
|
42
55
|
|
|
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
|
-
@dataclass
|
|
53
|
-
class MockResult:
|
|
54
|
-
values: list[Any]
|
|
55
|
-
columns: list[str]
|
|
56
|
-
|
|
57
|
-
def fetchall(self):
|
|
58
|
-
return self.values
|
|
59
|
-
|
|
60
|
-
def keys(self):
|
|
61
|
-
return self.columns
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def generate_result_set(
|
|
65
|
-
columns: List[ConceptRef], output_data: list[Any]
|
|
66
|
-
) -> MockResult:
|
|
67
|
-
names = [x.address.replace(".", "_") for x in columns]
|
|
68
|
-
return MockResult(
|
|
69
|
-
values=[dict(zip(names, [row])) for row in output_data], columns=names
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
|
|
73
56
|
class Executor(object):
|
|
74
57
|
def __init__(
|
|
75
58
|
self,
|
|
@@ -93,141 +76,93 @@ class Executor(object):
|
|
|
93
76
|
|
|
94
77
|
def execute_statement(
|
|
95
78
|
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
|
-
):
|
|
79
|
+
statement: PROCESSED_STATEMENT_TYPES,
|
|
80
|
+
) -> Optional[ResultProtocol]:
|
|
81
|
+
if not isinstance(statement, PROCESSED_STATEMENT_TYPES):
|
|
114
82
|
return None
|
|
115
83
|
return self.execute_query(statement)
|
|
116
84
|
|
|
117
85
|
@singledispatchmethod
|
|
118
|
-
def execute_query(self, query) ->
|
|
86
|
+
def execute_query(self, query) -> ResultProtocol | None:
|
|
119
87
|
raise NotImplementedError("Cannot execute type {}".format(type(query)))
|
|
120
88
|
|
|
121
89
|
@execute_query.register
|
|
122
|
-
def _(self, query: ConceptDeclarationStatement) ->
|
|
123
|
-
|
|
124
|
-
return MockResult(
|
|
125
|
-
[
|
|
126
|
-
{
|
|
127
|
-
"address": concept.address,
|
|
128
|
-
"type": concept.datatype.value,
|
|
129
|
-
"purpose": concept.purpose.value,
|
|
130
|
-
"derivation": concept.derivation.value,
|
|
131
|
-
}
|
|
132
|
-
],
|
|
133
|
-
["address", "type", "purpose", "derivation"],
|
|
134
|
-
)
|
|
90
|
+
def _(self, query: ConceptDeclarationStatement) -> ResultProtocol | None:
|
|
91
|
+
return handle_concept_declaration(query)
|
|
135
92
|
|
|
136
93
|
@execute_query.register
|
|
137
|
-
def _(self, query: Datasource) ->
|
|
138
|
-
return
|
|
139
|
-
[
|
|
140
|
-
{
|
|
141
|
-
"name": query.name,
|
|
142
|
-
}
|
|
143
|
-
],
|
|
144
|
-
["name"],
|
|
145
|
-
)
|
|
94
|
+
def _(self, query: Datasource) -> ResultProtocol | None:
|
|
95
|
+
return handle_datasource(query)
|
|
146
96
|
|
|
147
97
|
@execute_query.register
|
|
148
|
-
def _(self, query: str) ->
|
|
98
|
+
def _(self, query: str) -> ResultProtocol | None:
|
|
149
99
|
results = self.execute_text(query)
|
|
150
100
|
if results:
|
|
151
101
|
return results[-1]
|
|
152
102
|
return None
|
|
153
103
|
|
|
154
104
|
@execute_query.register
|
|
155
|
-
def _(self, query: SelectStatement) ->
|
|
105
|
+
def _(self, query: SelectStatement) -> ResultProtocol | None:
|
|
156
106
|
sql = self.generator.generate_queries(
|
|
157
107
|
self.environment, [query], hooks=self.hooks
|
|
158
108
|
)
|
|
159
109
|
return self.execute_query(sql[0])
|
|
160
110
|
|
|
161
111
|
@execute_query.register
|
|
162
|
-
def _(self, query: PersistStatement) ->
|
|
112
|
+
def _(self, query: PersistStatement) -> ResultProtocol | None:
|
|
163
113
|
sql = self.generator.generate_queries(
|
|
164
114
|
self.environment, [query], hooks=self.hooks
|
|
165
115
|
)
|
|
166
116
|
return self.execute_query(sql[0])
|
|
167
117
|
|
|
168
118
|
@execute_query.register
|
|
169
|
-
def _(self, query: RawSQLStatement) ->
|
|
119
|
+
def _(self, query: RawSQLStatement) -> ResultProtocol | None:
|
|
170
120
|
return self.execute_raw_sql(query.text)
|
|
171
121
|
|
|
172
122
|
@execute_query.register
|
|
173
|
-
def _(self, query: ShowStatement) ->
|
|
123
|
+
def _(self, query: ShowStatement) -> ResultProtocol | None:
|
|
174
124
|
sql = self.generator.generate_queries(
|
|
175
125
|
self.environment, [query], hooks=self.hooks
|
|
176
126
|
)
|
|
177
127
|
return self.execute_query(sql[0])
|
|
178
128
|
|
|
179
129
|
@execute_query.register
|
|
180
|
-
def _(self, query: ProcessedShowStatement) ->
|
|
181
|
-
return
|
|
182
|
-
query
|
|
130
|
+
def _(self, query: ProcessedShowStatement) -> ResultProtocol | None:
|
|
131
|
+
return handle_processed_show_statement(
|
|
132
|
+
query,
|
|
183
133
|
[
|
|
184
134
|
self.generator.compile_statement(x)
|
|
185
135
|
for x in query.output_values
|
|
186
|
-
if isinstance(x, ProcessedQuery)
|
|
136
|
+
if isinstance(x, (ProcessedQuery, ProcessedQueryPersist))
|
|
187
137
|
],
|
|
188
138
|
)
|
|
189
139
|
|
|
190
140
|
@execute_query.register
|
|
191
|
-
def _(self, query:
|
|
192
|
-
return
|
|
193
|
-
|
|
194
|
-
{
|
|
195
|
-
"path": query.path,
|
|
196
|
-
"alias": query.alias,
|
|
197
|
-
}
|
|
198
|
-
],
|
|
199
|
-
["path", "alias"],
|
|
141
|
+
def _(self, query: ProcessedValidateStatement) -> ResultProtocol | None:
|
|
142
|
+
return handle_processed_validate_statement(
|
|
143
|
+
query, self.generator, self.validate_environment
|
|
200
144
|
)
|
|
201
145
|
|
|
202
146
|
@execute_query.register
|
|
203
|
-
def _(self, query:
|
|
204
|
-
|
|
205
|
-
self.environment.merge_concept(
|
|
206
|
-
concept, query.targets[concept.address], modifiers=query.modifiers
|
|
207
|
-
)
|
|
147
|
+
def _(self, query: ImportStatement) -> ResultProtocol | None:
|
|
148
|
+
return handle_import_statement(query)
|
|
208
149
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
"sources": ",".join([x.address for x in query.sources]),
|
|
213
|
-
"targets": ",".join([x.address for _, x in query.targets.items()]),
|
|
214
|
-
}
|
|
215
|
-
],
|
|
216
|
-
["source", "target"],
|
|
217
|
-
)
|
|
150
|
+
@execute_query.register
|
|
151
|
+
def _(self, query: MergeStatementV2) -> ResultProtocol | None:
|
|
152
|
+
return handle_merge_statement(query, self.environment)
|
|
218
153
|
|
|
219
154
|
@execute_query.register
|
|
220
|
-
def _(self, query: ProcessedRawSQLStatement) ->
|
|
155
|
+
def _(self, query: ProcessedRawSQLStatement) -> ResultProtocol | None:
|
|
221
156
|
return self.execute_raw_sql(query.text)
|
|
222
157
|
|
|
223
158
|
@execute_query.register
|
|
224
|
-
def _(self, query: ProcessedQuery) ->
|
|
159
|
+
def _(self, query: ProcessedQuery) -> ResultProtocol | None:
|
|
225
160
|
sql = self.generator.compile_statement(query)
|
|
226
161
|
output = self.execute_raw_sql(sql, local_concepts=query.local_concepts)
|
|
227
162
|
return output
|
|
228
163
|
|
|
229
164
|
@execute_query.register
|
|
230
|
-
def _(self, query: ProcessedQueryPersist) ->
|
|
165
|
+
def _(self, query: ProcessedQueryPersist) -> ResultProtocol | None:
|
|
231
166
|
sql = self.generator.compile_statement(query)
|
|
232
167
|
|
|
233
168
|
output = self.execute_raw_sql(sql, local_concepts=query.local_concepts)
|
|
@@ -235,9 +170,9 @@ class Executor(object):
|
|
|
235
170
|
return output
|
|
236
171
|
|
|
237
172
|
@execute_query.register
|
|
238
|
-
def _(self, query: ProcessedCopyStatement) ->
|
|
173
|
+
def _(self, query: ProcessedCopyStatement) -> ResultProtocol | None:
|
|
239
174
|
sql = self.generator.compile_statement(query)
|
|
240
|
-
output:
|
|
175
|
+
output: ResultProtocol = self.execute_raw_sql(
|
|
241
176
|
sql, local_concepts=query.local_concepts
|
|
242
177
|
)
|
|
243
178
|
if query.target_type == IOType.CSV:
|
|
@@ -278,8 +213,6 @@ class Executor(object):
|
|
|
278
213
|
for statement in sql:
|
|
279
214
|
compiled_sql = self.generator.compile_statement(statement)
|
|
280
215
|
output.append(compiled_sql)
|
|
281
|
-
|
|
282
|
-
output.append(compiled_sql)
|
|
283
216
|
return output
|
|
284
217
|
|
|
285
218
|
@generate_sql.register # type: ignore
|
|
@@ -313,23 +246,13 @@ class Executor(object):
|
|
|
313
246
|
|
|
314
247
|
def parse_file(
|
|
315
248
|
self, file: str | Path, persist: bool = False
|
|
316
|
-
) -> list[
|
|
317
|
-
ProcessedQuery
|
|
318
|
-
| ProcessedQueryPersist
|
|
319
|
-
| ProcessedShowStatement
|
|
320
|
-
| ProcessedRawSQLStatement
|
|
321
|
-
| ProcessedCopyStatement,
|
|
322
|
-
]:
|
|
249
|
+
) -> list[PROCESSED_STATEMENT_TYPES]:
|
|
323
250
|
return list(self.parse_file_generator(file, persist=persist))
|
|
324
251
|
|
|
325
252
|
def parse_file_generator(
|
|
326
253
|
self, file: str | Path, persist: bool = False
|
|
327
254
|
) -> Generator[
|
|
328
|
-
|
|
329
|
-
| ProcessedQueryPersist
|
|
330
|
-
| ProcessedShowStatement
|
|
331
|
-
| ProcessedRawSQLStatement
|
|
332
|
-
| ProcessedCopyStatement,
|
|
255
|
+
PROCESSED_STATEMENT_TYPES,
|
|
333
256
|
None,
|
|
334
257
|
None,
|
|
335
258
|
]:
|
|
@@ -340,23 +263,13 @@ class Executor(object):
|
|
|
340
263
|
|
|
341
264
|
def parse_text(
|
|
342
265
|
self, command: str, persist: bool = False, root: Path | None = None
|
|
343
|
-
) -> List[
|
|
344
|
-
ProcessedQuery
|
|
345
|
-
| ProcessedQueryPersist
|
|
346
|
-
| ProcessedShowStatement
|
|
347
|
-
| ProcessedRawSQLStatement
|
|
348
|
-
| ProcessedCopyStatement
|
|
349
|
-
]:
|
|
266
|
+
) -> List[PROCESSED_STATEMENT_TYPES]:
|
|
350
267
|
return list(self.parse_text_generator(command, persist=persist, root=root))
|
|
351
268
|
|
|
352
269
|
def parse_text_generator(
|
|
353
270
|
self, command: str, persist: bool = False, root: Path | None = None
|
|
354
271
|
) -> Generator[
|
|
355
|
-
|
|
356
|
-
| ProcessedQueryPersist
|
|
357
|
-
| ProcessedShowStatement
|
|
358
|
-
| ProcessedRawSQLStatement
|
|
359
|
-
| ProcessedCopyStatement,
|
|
272
|
+
PROCESSED_STATEMENT_TYPES,
|
|
360
273
|
None,
|
|
361
274
|
None,
|
|
362
275
|
]:
|
|
@@ -374,6 +287,7 @@ class Executor(object):
|
|
|
374
287
|
ShowStatement,
|
|
375
288
|
RawSQLStatement,
|
|
376
289
|
CopyStatement,
|
|
290
|
+
ValidateStatement,
|
|
377
291
|
),
|
|
378
292
|
)
|
|
379
293
|
]
|
|
@@ -420,10 +334,12 @@ class Executor(object):
|
|
|
420
334
|
# return self._concept_to_value(self.environment.concepts[rval.address], local_concepts=local_concepts)
|
|
421
335
|
return rval
|
|
422
336
|
else:
|
|
423
|
-
results = self.execute_query(f"select {concept.name} limit 1;")
|
|
424
|
-
|
|
337
|
+
results = self.execute_query(f"select {concept.name} limit 1;")
|
|
338
|
+
if results:
|
|
339
|
+
fetcher = results.fetchone()
|
|
340
|
+
if fetcher:
|
|
341
|
+
return fetcher[0]
|
|
425
342
|
return None
|
|
426
|
-
return results[0]
|
|
427
343
|
|
|
428
344
|
def _hydrate_param(
|
|
429
345
|
self, param: str, local_concepts: dict[str, Concept] | None = None
|
|
@@ -447,13 +363,16 @@ class Executor(object):
|
|
|
447
363
|
|
|
448
364
|
def execute_raw_sql(
|
|
449
365
|
self,
|
|
450
|
-
command: str,
|
|
366
|
+
command: str | Path,
|
|
451
367
|
variables: dict | None = None,
|
|
452
368
|
local_concepts: dict[str, Concept] | None = None,
|
|
453
|
-
) ->
|
|
369
|
+
) -> ResultProtocol:
|
|
454
370
|
"""Run a command against the raw underlying
|
|
455
371
|
execution engine."""
|
|
456
372
|
final_params = None
|
|
373
|
+
if isinstance(command, Path):
|
|
374
|
+
with open(command, "r") as f:
|
|
375
|
+
command = f.read()
|
|
457
376
|
q = text(command)
|
|
458
377
|
if variables:
|
|
459
378
|
final_params = variables
|
|
@@ -473,37 +392,50 @@ class Executor(object):
|
|
|
473
392
|
|
|
474
393
|
def execute_text(
|
|
475
394
|
self, command: str, non_interactive: bool = False
|
|
476
|
-
) -> List[
|
|
395
|
+
) -> List[ResultProtocol]:
|
|
477
396
|
"""Run a trilogy query expressed as text."""
|
|
478
|
-
output = []
|
|
397
|
+
output: list[ResultProtocol] = []
|
|
479
398
|
# connection = self.engine.connect()
|
|
480
399
|
for statement in self.parse_text_generator(command):
|
|
481
400
|
if isinstance(statement, ProcessedShowStatement):
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
)
|
|
401
|
+
results = handle_show_statement_outputs(
|
|
402
|
+
statement,
|
|
403
|
+
[
|
|
404
|
+
self.generator.compile_statement(x)
|
|
405
|
+
for x in statement.output_values
|
|
406
|
+
if isinstance(x, (ProcessedQuery, ProcessedQueryPersist))
|
|
407
|
+
],
|
|
408
|
+
self.environment,
|
|
409
|
+
self.generator,
|
|
410
|
+
)
|
|
411
|
+
output.extend(results)
|
|
494
412
|
continue
|
|
495
413
|
if non_interactive:
|
|
496
414
|
if not isinstance(
|
|
497
415
|
statement, (ProcessedCopyStatement, ProcessedQueryPersist)
|
|
498
416
|
):
|
|
499
417
|
continue
|
|
500
|
-
|
|
418
|
+
result = self.execute_statement(statement)
|
|
419
|
+
if result:
|
|
420
|
+
output.append(result)
|
|
501
421
|
return output
|
|
502
422
|
|
|
503
423
|
def execute_file(
|
|
504
424
|
self, file: str | Path, non_interactive: bool = False
|
|
505
|
-
) -> List[
|
|
425
|
+
) -> List[ResultProtocol]:
|
|
506
426
|
file = Path(file)
|
|
507
427
|
with open(file, "r") as f:
|
|
508
428
|
command = f.read()
|
|
509
429
|
return self.execute_text(command, non_interactive=non_interactive)
|
|
430
|
+
|
|
431
|
+
def validate_environment(
|
|
432
|
+
self,
|
|
433
|
+
scope: ValidationScope = ValidationScope.ALL,
|
|
434
|
+
targets: Optional[List[str]] = None,
|
|
435
|
+
generate_only: bool = False,
|
|
436
|
+
) -> list[ValidationTest]:
|
|
437
|
+
from trilogy.core.validation.environment import validate_environment
|
|
438
|
+
|
|
439
|
+
return validate_environment(
|
|
440
|
+
self.environment, scope, targets, exec=None if generate_only else self
|
|
441
|
+
)
|
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,
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|