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.

Files changed (39) hide show
  1. {pytrilogy-0.0.3.93.dist-info → pytrilogy-0.0.3.95.dist-info}/METADATA +170 -145
  2. {pytrilogy-0.0.3.93.dist-info → pytrilogy-0.0.3.95.dist-info}/RECORD +38 -34
  3. trilogy/__init__.py +1 -1
  4. trilogy/authoring/__init__.py +4 -0
  5. trilogy/core/enums.py +13 -0
  6. trilogy/core/env_processor.py +21 -10
  7. trilogy/core/environment_helpers.py +111 -0
  8. trilogy/core/exceptions.py +21 -1
  9. trilogy/core/functions.py +6 -1
  10. trilogy/core/graph_models.py +60 -67
  11. trilogy/core/internal.py +18 -0
  12. trilogy/core/models/author.py +16 -25
  13. trilogy/core/models/build.py +5 -4
  14. trilogy/core/models/core.py +3 -0
  15. trilogy/core/models/environment.py +28 -0
  16. trilogy/core/models/execute.py +7 -0
  17. trilogy/core/processing/node_generators/node_merge_node.py +30 -28
  18. trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +25 -11
  19. trilogy/core/processing/node_generators/select_merge_node.py +68 -82
  20. trilogy/core/query_processor.py +2 -1
  21. trilogy/core/statements/author.py +18 -3
  22. trilogy/core/statements/common.py +0 -10
  23. trilogy/core/statements/execute.py +71 -16
  24. trilogy/core/validation/__init__.py +0 -0
  25. trilogy/core/validation/common.py +109 -0
  26. trilogy/core/validation/concept.py +122 -0
  27. trilogy/core/validation/datasource.py +192 -0
  28. trilogy/core/validation/environment.py +71 -0
  29. trilogy/dialect/base.py +40 -21
  30. trilogy/dialect/sql_server.py +3 -1
  31. trilogy/engine.py +25 -7
  32. trilogy/executor.py +145 -83
  33. trilogy/parsing/parse_engine.py +35 -4
  34. trilogy/parsing/trilogy.lark +11 -5
  35. trilogy/core/processing/node_generators/select_merge_node_v2.py +0 -792
  36. {pytrilogy-0.0.3.93.dist-info → pytrilogy-0.0.3.95.dist-info}/WHEEL +0 -0
  37. {pytrilogy-0.0.3.93.dist-info → pytrilogy-0.0.3.95.dist-info}/entry_points.txt +0 -0
  38. {pytrilogy-0.0.3.93.dist-info → pytrilogy-0.0.3.95.dist-info}/licenses/LICENSE.md +0 -0
  39. {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, Protocol
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
- ProcessedQuery
98
- | ProcessedCopyStatement
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) -> CursorResult:
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) -> CursorResult:
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) -> CursorResult:
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) -> CursorResult | None:
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) -> CursorResult:
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) -> CursorResult:
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) -> CursorResult:
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) -> CursorResult:
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) -> CursorResult:
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: ImportStatement) -> CursorResult:
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) -> CursorResult:
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) -> CursorResult:
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) -> CursorResult:
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) -> CursorResult:
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) -> CursorResult:
291
+ def _(self, query: ProcessedCopyStatement) -> ResultProtocol | None:
239
292
  sql = self.generator.compile_statement(query)
240
- output: CursorResult = self.execute_raw_sql(
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
- ProcessedQuery
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
- ProcessedQuery
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;").fetchone()
424
- if not results:
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
- ) -> CursorResult:
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[CursorResult]:
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
- output.append(self.execute_query(statement))
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[CursorResult]:
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
+ )
@@ -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
- args = [self.environment.concepts[a] for a in args]
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 CurrentDatetime([])
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
  ]:
@@ -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: "group" IDENTIFIER "BY"i (IDENTIFIER ",")* IDENTIFIER
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 "=" literal ",")* IDENTIFIER "=" literal ","? ")"
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