pytrilogy 0.0.3.75__py3-none-any.whl → 0.0.3.77__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.75
3
+ Version: 0.0.3.77
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,9 +1,9 @@
1
- pytrilogy-0.0.3.75.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
- trilogy/__init__.py,sha256=MIf9sLTcNq9Rl4yjZRDALK9URcm1vSxXzixHDnfPJg0,303
1
+ pytrilogy-0.0.3.77.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
+ trilogy/__init__.py,sha256=MaNvYTE25EzVDkpklTQu2tZ8pSfm2j1aCH9wBRXrMpY,303
3
3
  trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  trilogy/constants.py,sha256=eKb_EJvSqjN9tGbdVEViwdtwwh8fZ3-jpOEDqL71y70,1691
5
5
  trilogy/engine.py,sha256=OK2RuqCIUId6yZ5hfF8J1nxGP0AJqHRZiafcowmW0xc,1728
6
- trilogy/executor.py,sha256=BolR6UwgDOdIcDG0gw_OSaB23rISgIn8Dzdll0kODmg,16506
6
+ trilogy/executor.py,sha256=iwrYs5hEaw2hTaNZOYW5Z0w6Va1RzdRpg5bn50tlslA,16731
7
7
  trilogy/parser.py,sha256=o4cfk3j3yhUFoiDKq9ZX_GjBF3dKhDjXEwb63rcBkBM,293
8
8
  trilogy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  trilogy/render.py,sha256=qQWwduymauOlB517UtM-VGbVe8Cswa4UJub5aGbSO6c,1512
@@ -11,20 +11,20 @@ trilogy/utility.py,sha256=euQccZLKoYBz0LNg5tzLlvv2YHvXh9HArnYp1V3uXsM,763
11
11
  trilogy/authoring/__init__.py,sha256=e74k-Jep4DLYPQU_2m0aVsYlw5HKTOucAKtdTbd6f2g,2595
12
12
  trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  trilogy/core/constants.py,sha256=nizWYDCJQ1bigQMtkNIEMNTcN0NoEAXiIHLzpelxQ24,201
14
- trilogy/core/enums.py,sha256=--iG8Q1NJ9JBJe4IIdO37P2T3TOWn_CmqbagsQGfNf0,8050
14
+ trilogy/core/enums.py,sha256=tDUXVWxvWgnO1u7LLS0iIReeHYbyFLN2ZbpTdUyKdjE,8060
15
15
  trilogy/core/env_processor.py,sha256=pFsxnluKIusGKx1z7tTnfsd_xZcPy9pZDungkjkyvI0,3170
16
16
  trilogy/core/environment_helpers.py,sha256=VvPIiFemqaLLpIpLIqprfu63K7muZ1YzNg7UZIUph8w,8267
17
17
  trilogy/core/ergonomics.py,sha256=e-7gE29vPLFdg0_A1smQ7eOrUwKl5VYdxRSTddHweRA,1631
18
18
  trilogy/core/exceptions.py,sha256=jYEduuMehcMkmCpf-OC_taELPZm7qNfeSNzIWkDYScs,707
19
19
  trilogy/core/functions.py,sha256=wQ5yCvMKXSCkaSo7yiwlGY0wvqE4Sm2MI9e5NEr2ORw,30933
20
20
  trilogy/core/graph_models.py,sha256=BYhJzHKSgnZHVLJs1CfsgrxTPHqKqPNeA64RlozGY0A,3498
21
- trilogy/core/internal.py,sha256=iicDBlC6nM8d7e7jqzf_ZOmpUsW8yrr2AA8AqEiLx-s,1577
21
+ trilogy/core/internal.py,sha256=wFx4e1I0mtx159YFShSXeUBSQ82NINtAbOI-92RX4i8,2151
22
22
  trilogy/core/optimization.py,sha256=ojpn-p79lr03SSVQbbw74iPCyoYpDYBmj1dbZ3oXCjI,8860
23
23
  trilogy/core/query_processor.py,sha256=5aFgv-2LVM1Uku9cR_tFuTRDwyLnxc95bCMAHeFy2AY,20332
24
24
  trilogy/core/utility.py,sha256=3VC13uSQWcZNghgt7Ot0ZTeEmNqs__cx122abVq9qhM,410
25
25
  trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- trilogy/core/models/author.py,sha256=iCj5zQcBYB-4u93vM9OfX56-eqRcWQhKRAwrHXRsNGA,78046
27
- trilogy/core/models/build.py,sha256=UfWuG86O8yivqK4UhCOXk5IyHUw0vZPckcSljeCuK3o,65859
26
+ trilogy/core/models/author.py,sha256=9gQ2UBJ2qZ4ngFOXei4VbYQAp8UTrcAr_5GvWW2O2l8,78192
27
+ trilogy/core/models/build.py,sha256=0TRcL__hvajLcpFwYeLjCV35cow5oVPbJHXA3oO09XA,65931
28
28
  trilogy/core/models/build_environment.py,sha256=s_C9xAHuD3yZ26T15pWVBvoqvlp2LdZ8yjsv2_HdXLk,5363
29
29
  trilogy/core/models/core.py,sha256=q-8ExAaSB3PoSDX2XOqfZ01HBBvT1pZFs7jGg1vZizc,11096
30
30
  trilogy/core/models/datasource.py,sha256=wogTevZ-9CyUW2a8gjzqMCieircxi-J5lkI7EOAZnck,9596
@@ -74,9 +74,9 @@ trilogy/core/statements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
74
74
  trilogy/core/statements/author.py,sha256=6cGCuKERNkH22T6iTsgoNp5CcIFwknF3WX-UmegbUPA,15409
75
75
  trilogy/core/statements/build.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
76
  trilogy/core/statements/common.py,sha256=KxEmz2ySySyZ6CTPzn0fJl5NX2KOk1RPyuUSwWhnK1g,759
77
- trilogy/core/statements/execute.py,sha256=rqfuoMuXPcH7L7TmE1dSiZ_K_A1ohB8whVMfGimZBOk,1294
77
+ trilogy/core/statements/execute.py,sha256=pfr1CZ_Cx1qQ-7LDyRI0JUfvtxBr_GGv-VeqiAjr43g,1406
78
78
  trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
- trilogy/dialect/base.py,sha256=fEkPou4Eku7Un9NLZ6ya8OIVdaaV8ZJyZIbn56xOrVc,45829
79
+ trilogy/dialect/base.py,sha256=aLJ7-Lq7TTe7I_FjMzhkC5LfrqyjN55AFD8T0lqsiRs,47202
80
80
  trilogy/dialect/bigquery.py,sha256=8xhEu0z_lKANjbvzvBbC7CeKrJf1iP8YyrHqNale-ug,4351
81
81
  trilogy/dialect/common.py,sha256=tSthIZOXXRPQ4KeMKnDDsH7KlTmf2EVqigVtLyoc4zc,6071
82
82
  trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
@@ -97,9 +97,9 @@ trilogy/parsing/common.py,sha256=yV1AckK0h8u1OFeGQBTMu-wuW5m63c5CcZuPicsTH_w,306
97
97
  trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
98
98
  trilogy/parsing/exceptions.py,sha256=Xwwsv2C9kSNv2q-HrrKC1f60JNHShXcCMzstTSEbiCw,154
99
99
  trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
100
- trilogy/parsing/parse_engine.py,sha256=XQa8kSLiXd--MH89HvwlyNClQnFJ7ZLkm8PwxMsxHeY,74587
100
+ trilogy/parsing/parse_engine.py,sha256=c8NzO4xR3egV-tKS02X6Ja5P4-QfWtnTzkfEn0eKYeU,76660
101
101
  trilogy/parsing/render.py,sha256=HSNntD82GiiwHT-TWPLXAaIMWLYVV5B5zQEsgwrHIBE,19605
102
- trilogy/parsing/trilogy.lark,sha256=v2UqMBhU9hn9km4mvQkk3E2wsEZjJ4cz4wU72NFFvU4,14993
102
+ trilogy/parsing/trilogy.lark,sha256=wiGXJdKfPTG7E_XdkN1rf9g9Yy1-UMVAXyTxtrBPm9w,15037
103
103
  trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
104
104
  trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
105
105
  trilogy/std/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -110,8 +110,8 @@ trilogy/std/money.preql,sha256=XWwvAV3WxBsHX9zfptoYRnBigcfYwrYtBHXTME0xJuQ,2082
110
110
  trilogy/std/net.preql,sha256=7l7MqIjs6TDCpO6dBAoNJU81Ex255jZRK36kBgE1GDs,158
111
111
  trilogy/std/ranking.preql,sha256=LDoZrYyz4g3xsII9XwXfmstZD-_92i1Eox1UqkBIfi8,83
112
112
  trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
113
- pytrilogy-0.0.3.75.dist-info/METADATA,sha256=BiUDu-P-eT6dmtLstg2EmFFJ0Pc-eU6k17U--NmkXqU,9734
114
- pytrilogy-0.0.3.75.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
115
- pytrilogy-0.0.3.75.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
116
- pytrilogy-0.0.3.75.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
117
- pytrilogy-0.0.3.75.dist-info/RECORD,,
113
+ pytrilogy-0.0.3.77.dist-info/METADATA,sha256=LApdnLN-kkLR7hIwuXiUQuAS_cFqfzITIcXqME1p0Jo,9734
114
+ pytrilogy-0.0.3.77.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
115
+ pytrilogy-0.0.3.77.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
116
+ pytrilogy-0.0.3.77.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
117
+ pytrilogy-0.0.3.77.dist-info/RECORD,,
trilogy/__init__.py CHANGED
@@ -4,6 +4,6 @@ from trilogy.dialect.enums import Dialects
4
4
  from trilogy.executor import Executor
5
5
  from trilogy.parser import parse
6
6
 
7
- __version__ = "0.0.3.75"
7
+ __version__ = "0.0.3.77"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
trilogy/core/enums.py CHANGED
@@ -353,7 +353,7 @@ class SourceType(Enum):
353
353
 
354
354
 
355
355
  class ShowCategory(Enum):
356
- MODELS = "models"
356
+ DATASOURCES = "datasources"
357
357
  CONCEPTS = "concepts"
358
358
 
359
359
 
trilogy/core/internal.py CHANGED
@@ -19,8 +19,26 @@ DEFAULT_CONCEPTS = {
19
19
  granularity=Granularity.SINGLE_ROW,
20
20
  derivation=Derivation.CONSTANT,
21
21
  ),
22
- "concept_name": Concept(
23
- name="concept_name",
22
+ "concept_address": Concept(
23
+ name="concept_address",
24
+ namespace=INTERNAL_NAMESPACE,
25
+ datatype=DataType.STRING,
26
+ purpose=Purpose.KEY,
27
+ grain=Grain(),
28
+ granularity=Granularity.SINGLE_ROW,
29
+ derivation=Derivation.CONSTANT,
30
+ ),
31
+ "concept_datatype": Concept(
32
+ name="concept_datatype",
33
+ namespace=INTERNAL_NAMESPACE,
34
+ datatype=DataType.STRING,
35
+ purpose=Purpose.KEY,
36
+ grain=Grain(),
37
+ granularity=Granularity.SINGLE_ROW,
38
+ derivation=Derivation.CONSTANT,
39
+ ),
40
+ "concept_description": Concept(
41
+ name="concept_description",
24
42
  namespace=INTERNAL_NAMESPACE,
25
43
  datatype=DataType.STRING,
26
44
  purpose=Purpose.KEY,
@@ -77,7 +77,7 @@ class Mergeable(ABC):
77
77
  def with_merge(self, source: Concept, target: Concept, modifiers: List[Modifier]):
78
78
  raise NotImplementedError
79
79
 
80
- def with_reference_replacement(self, source: str, target: Expr):
80
+ def with_reference_replacement(self, source: str, target: Expr | ArgBinding):
81
81
  raise NotImplementedError(type(self))
82
82
 
83
83
 
@@ -161,7 +161,7 @@ class ConceptRef(Addressable, Namespaced, DataTyped, Mergeable, BaseModel):
161
161
  metadata=self.metadata,
162
162
  )
163
163
 
164
- def with_reference_replacement(self, source: str, target: Expr):
164
+ def with_reference_replacement(self, source: str, target: Expr | ArgBinding):
165
165
  if self.address == source:
166
166
  return target
167
167
  return self
@@ -1708,7 +1708,7 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1708
1708
  )
1709
1709
  return v
1710
1710
 
1711
- def with_reference_replacement(self, source: str, target: Expr):
1711
+ def with_reference_replacement(self, source: str, target: Expr | ArgBinding):
1712
1712
  from trilogy.core.functions import arg_to_datatype, merge_datatypes
1713
1713
 
1714
1714
  nargs = [
@@ -2303,13 +2303,13 @@ class CustomFunctionFactory:
2303
2303
  ]
2304
2304
  return self
2305
2305
 
2306
- def __call__(self, *creation_args: Expr):
2306
+ def __call__(self, *creation_args: ArgBinding | Expr):
2307
2307
  nout = (
2308
2308
  self.function.model_copy(deep=True)
2309
2309
  if isinstance(self.function, BaseModel)
2310
2310
  else self.function
2311
2311
  )
2312
- creation_arg_list: list[Expr] = list(creation_args)
2312
+ creation_arg_list: list[ArgBinding | Expr] = list(creation_args)
2313
2313
  if len(creation_args) < len(self.function_arguments):
2314
2314
  for binding in self.function_arguments[len(creation_arg_list) :]:
2315
2315
  if binding.default is None:
@@ -2361,7 +2361,9 @@ class Comment(BaseModel):
2361
2361
  class ArgBinding(Namespaced, DataTyped, BaseModel):
2362
2362
  name: str
2363
2363
  default: Expr | None = None
2364
- datatype: DataType = DataType.UNKNOWN
2364
+ datatype: (
2365
+ DataType | MapType | ArrayType | NumericType | StructType | TraitDataType
2366
+ ) = DataType.UNKNOWN
2365
2367
 
2366
2368
  def with_namespace(self, namespace):
2367
2369
  return ArgBinding(
@@ -606,7 +606,6 @@ class BuildComparison(BuildConceptArgs, ConstantInlineable, BaseModel):
606
606
  int,
607
607
  str,
608
608
  float,
609
- list,
610
609
  bool,
611
610
  datetime,
612
611
  date,
@@ -619,12 +618,13 @@ class BuildComparison(BuildConceptArgs, ConstantInlineable, BaseModel):
619
618
  MagicConstants,
620
619
  BuildWindowItem,
621
620
  BuildAggregateWrapper,
621
+ ListWrapper,
622
+ TupleWrapper,
622
623
  ]
623
624
  right: Union[
624
625
  int,
625
626
  str,
626
627
  float,
627
- list,
628
628
  bool,
629
629
  date,
630
630
  datetime,
@@ -638,6 +638,7 @@ class BuildComparison(BuildConceptArgs, ConstantInlineable, BaseModel):
638
638
  BuildWindowItem,
639
639
  BuildAggregateWrapper,
640
640
  TupleWrapper,
641
+ ListWrapper,
641
642
  ]
642
643
  operator: ComparisonOperator
643
644
 
@@ -734,7 +735,6 @@ class BuildSubselectComparison(BuildComparison):
734
735
  int,
735
736
  str,
736
737
  float,
737
- list,
738
738
  bool,
739
739
  datetime,
740
740
  date,
@@ -747,12 +747,13 @@ class BuildSubselectComparison(BuildComparison):
747
747
  MagicConstants,
748
748
  BuildWindowItem,
749
749
  BuildAggregateWrapper,
750
+ ListWrapper,
751
+ TupleWrapper,
750
752
  ]
751
753
  right: Union[
752
754
  int,
753
755
  str,
754
756
  float,
755
- list,
756
757
  bool,
757
758
  date,
758
759
  datetime,
@@ -766,6 +767,7 @@ class BuildSubselectComparison(BuildComparison):
766
767
  BuildWindowItem,
767
768
  BuildAggregateWrapper,
768
769
  TupleWrapper,
770
+ ListWrapper,
769
771
  ]
770
772
  operator: ComparisonOperator
771
773
 
@@ -38,6 +38,12 @@ class ProcessedRawSQLStatement(BaseModel):
38
38
  text: str
39
39
 
40
40
 
41
+ class ProcessedStaticValueOutput(BaseModel):
42
+ values: List[dict]
43
+
44
+
41
45
  class ProcessedShowStatement(BaseModel):
42
46
  output_columns: List[ConceptRef]
43
- output_values: List[Union[BuildConcept, BuildDatasource, ProcessedQuery]]
47
+ output_values: List[
48
+ Union[BuildConcept, BuildDatasource, ProcessedQuery, ProcessedStaticValueOutput]
49
+ ]
trilogy/dialect/base.py CHANGED
@@ -16,6 +16,7 @@ from trilogy.core.enums import (
16
16
  DatePart,
17
17
  FunctionType,
18
18
  Ordering,
19
+ ShowCategory,
19
20
  UnnestMode,
20
21
  WindowType,
21
22
  )
@@ -77,6 +78,7 @@ from trilogy.core.statements.execute import (
77
78
  ProcessedQueryPersist,
78
79
  ProcessedRawSQLStatement,
79
80
  ProcessedShowStatement,
81
+ ProcessedStaticValueOutput,
80
82
  )
81
83
  from trilogy.core.utility import safe_quote
82
84
  from trilogy.dialect.common import render_join, render_unnest
@@ -638,18 +640,7 @@ class BaseDialect:
638
640
  ):
639
641
  return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)} {e.operator.value} {self.render_expr(e.right, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)}"
640
642
 
641
- elif isinstance(
642
- e.right,
643
- (
644
- str,
645
- int,
646
- bool,
647
- float,
648
- ),
649
- ):
650
- return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)} {e.operator.value} ({self.render_expr(e.right, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)})"
651
- else:
652
- return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)} {e.operator.value} {self.render_expr(e.right, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)}"
643
+ return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)} {e.operator.value} ({self.render_expr(e.right, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)})"
653
644
  elif isinstance(e, COMPARISON_ITEMS):
654
645
  return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)} {e.operator.value} {self.render_expr(e.right, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)}"
655
646
  elif isinstance(e, CONDITIONAL_ITEMS):
@@ -953,6 +944,46 @@ class BaseDialect:
953
944
  self.render_cte(sort_select_output(query.ctes[-1], query), auto_sort=False)
954
945
  ]
955
946
 
947
+ def create_show_output(
948
+ self,
949
+ environment: Environment,
950
+ content: ShowCategory,
951
+ ):
952
+ if content == ShowCategory.CONCEPTS:
953
+ output_columns = [
954
+ environment.concepts[
955
+ DEFAULT_CONCEPTS["concept_address"].address
956
+ ].reference,
957
+ environment.concepts[
958
+ DEFAULT_CONCEPTS["concept_datatype"].address
959
+ ].reference,
960
+ environment.concepts[
961
+ DEFAULT_CONCEPTS["concept_description"].address
962
+ ].reference,
963
+ ]
964
+ output_values = [
965
+ {
966
+ DEFAULT_CONCEPTS["concept_address"].address: (
967
+ concept.name
968
+ if concept.namespace == DEFAULT_NAMESPACE
969
+ else concept.address
970
+ ),
971
+ DEFAULT_CONCEPTS["concept_datatype"].address: str(concept.datatype),
972
+ DEFAULT_CONCEPTS[
973
+ "concept_description"
974
+ ].address: concept.metadata.description
975
+ or "",
976
+ }
977
+ for _, concept in environment.concepts.items()
978
+ if not concept.is_internal
979
+ ]
980
+ else:
981
+ raise NotImplementedError(f"Show category {content} not implemented")
982
+ return ProcessedShowStatement(
983
+ output_columns=output_columns,
984
+ output_values=[ProcessedStaticValueOutput(values=output_values)],
985
+ )
986
+
956
987
  def generate_queries(
957
988
  self,
958
989
  environment: Environment,
@@ -1027,8 +1058,12 @@ class BaseDialect:
1027
1058
  ],
1028
1059
  )
1029
1060
  )
1061
+ elif isinstance(statement.content, ShowCategory):
1062
+ output.append(
1063
+ self.create_show_output(environment, statement.content)
1064
+ )
1030
1065
  else:
1031
- raise NotImplementedError(type(statement))
1066
+ raise NotImplementedError(type(statement.content))
1032
1067
  elif isinstance(statement, RawSQLStatement):
1033
1068
  output.append(ProcessedRawSQLStatement(text=statement.text))
1034
1069
  elif isinstance(
trilogy/executor.py CHANGED
@@ -30,6 +30,7 @@ from trilogy.core.statements.execute import (
30
30
  ProcessedQueryPersist,
31
31
  ProcessedRawSQLStatement,
32
32
  ProcessedShowStatement,
33
+ ProcessedStaticValueOutput,
33
34
  )
34
35
  from trilogy.dialect.base import BaseDialect
35
36
  from trilogy.dialect.enums import Dialects
@@ -470,16 +471,18 @@ class Executor(object):
470
471
  # connection = self.engine.connect()
471
472
  for statement in self.parse_text_generator(command):
472
473
  if isinstance(statement, ProcessedShowStatement):
473
- output.append(
474
- generate_result_set(
475
- statement.output_columns,
476
- [
477
- self.generator.compile_statement(x)
478
- for x in statement.output_values
479
- if isinstance(x, ProcessedQuery)
480
- ],
481
- )
482
- )
474
+ for x in statement.output_values:
475
+ if isinstance(x, ProcessedStaticValueOutput):
476
+ output.append(
477
+ generate_result_set(statement.output_columns, x.values)
478
+ )
479
+ elif isinstance(x, ProcessedQuery):
480
+ output.append(
481
+ generate_result_set(
482
+ statement.output_columns,
483
+ [self.generator.compile_statement(x)],
484
+ )
485
+ )
483
486
  continue
484
487
  if non_interactive:
485
488
  if not isinstance(
@@ -7,7 +7,7 @@ from pathlib import Path
7
7
  from re import IGNORECASE
8
8
  from typing import Any, List, Optional, Tuple, Union
9
9
 
10
- from lark import Lark, ParseTree, Transformer, Tree, v_args
10
+ from lark import Lark, ParseTree, Token, Transformer, Tree, v_args
11
11
  from lark.exceptions import (
12
12
  UnexpectedCharacters,
13
13
  UnexpectedEOF,
@@ -2034,12 +2034,17 @@ class ParseToObjects(Transformer):
2034
2034
  return self.environment.functions[args[0]]
2035
2035
 
2036
2036
  @v_args(meta=True)
2037
- def farray_transform(self, meta, args):
2037
+ def farray_transform(self, meta, args) -> Function:
2038
2038
  factory: CustomFunctionFactory = args[1]
2039
2039
  if not len(factory.function_arguments) == 1:
2040
2040
  raise InvalidSyntaxException(
2041
2041
  "Array transform function must have exactly one argument;"
2042
2042
  )
2043
+ array_type = arg_to_datatype(args[0])
2044
+ if not isinstance(array_type, ArrayType):
2045
+ raise InvalidSyntaxException(
2046
+ f"Array transform function must be applied to an array, not {array_type}"
2047
+ )
2043
2048
  return self.function_factory.create_function(
2044
2049
  [
2045
2050
  args[0],
@@ -2047,7 +2052,7 @@ class ParseToObjects(Transformer):
2047
2052
  factory(
2048
2053
  ArgBinding(
2049
2054
  name=factory.function_arguments[0].name,
2050
- datatype=arg_to_datatype(args[0]).value_data_type,
2055
+ datatype=array_type.value_data_type,
2051
2056
  )
2052
2057
  ),
2053
2058
  ],
@@ -2086,6 +2091,15 @@ def parse_text_raw(text: str, environment: Optional[Environment] = None):
2086
2091
  PARSER.parse(text)
2087
2092
 
2088
2093
 
2094
+ ERROR_CODES: dict[int, str] = {
2095
+ # 100 code are SQL compatability errors
2096
+ 101: "Trilogy does not have a FROM clause (Datasource resolution is automatic). Remove the FROM clause.",
2097
+ # 200 codes relate to required explicit syntax (we could loosen these?)
2098
+ 201: 'Alias must be specified with "AS" - e.g. `SELECT x AS y`',
2099
+ 210: "Order by must be explicit about direction - specify `asc` or `desc`.",
2100
+ }
2101
+
2102
+
2089
2103
  def parse_text(
2090
2104
  text: str,
2091
2105
  environment: Optional[Environment] = None,
@@ -2135,9 +2149,43 @@ def parse_text(
2135
2149
  ValidationError,
2136
2150
  TypeError,
2137
2151
  ) as e:
2138
- if isinstance(
2139
- e, (UnexpectedCharacters, UnexpectedEOF, UnexpectedInput, UnexpectedToken)
2140
- ):
2152
+ if isinstance(e, UnexpectedToken):
2153
+ if e.expected == {"ORDERING_DIRECTION"}:
2154
+ code = 210
2155
+ raise InvalidSyntaxException(
2156
+ f"Syntax [{code}]:"
2157
+ + ERROR_CODES[code]
2158
+ + "\nContext:\n"
2159
+ + e.get_context(text.replace("\n", " "), 20)
2160
+ )
2161
+ parsed_tokens = (
2162
+ [x.value for x in e.token_history] if e.token_history else []
2163
+ )
2164
+ if parsed_tokens == ["FROM"]:
2165
+ code = 101
2166
+ raise InvalidSyntaxException(
2167
+ f"Syntax [{code}]:"
2168
+ + ERROR_CODES[code]
2169
+ + "\nContext:\n"
2170
+ + e.get_context(text.replace("\n", " "), 20)
2171
+ )
2172
+ # recovery attempt for aliasing
2173
+ try:
2174
+ e.interactive_parser.feed_token(Token("AS", "AS"))
2175
+ e.interactive_parser.feed_token(e.token)
2176
+ raise InvalidSyntaxException(
2177
+ f"Syntax [{ERROR_CODES[201]}]:"
2178
+ + ERROR_CODES[201]
2179
+ + "\nContext:\n"
2180
+ + e.get_context(text.replace("\n", " "), 20)
2181
+ )
2182
+ except UnexpectedToken:
2183
+ pass
2184
+ raise InvalidSyntaxException(
2185
+ str(e) + "\nContext:\n" + e.get_context(text.replace("\n", " "), 20)
2186
+ )
2187
+ elif isinstance(e, (UnexpectedCharacters, UnexpectedEOF, UnexpectedInput)):
2188
+
2141
2189
  raise InvalidSyntaxException(
2142
2190
  str(e) + "\nContext:\n" + e.get_context(text.replace("\n", " "), 20)
2143
2191
  )
@@ -130,7 +130,9 @@
130
130
 
131
131
  over_list: concept_lit ("," concept_lit )* ","?
132
132
 
133
- !ordering: /ASC|DESC/i ("NULLS"i /FIRST|LAST|AUTO/i )?
133
+ ORDERING_DIRECTION: /ASC|DESC/i
134
+
135
+ !ordering: ORDERING_DIRECTION ("NULLS"i /FIRST|LAST|AUTO/i )?
134
136
 
135
137
  order_by: "ORDER"i "BY"i order_list
136
138