pytrilogy 0.0.3.8__py3-none-any.whl → 0.0.3.10__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.2
2
2
  Name: pytrilogy
3
- Version: 0.0.3.8
3
+ Version: 0.0.3.10
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,31 +1,31 @@
1
- trilogy/__init__.py,sha256=OtihFBeuWR7p7GBhiNMfyCpmu-x2kn_W2xSawos5ILM,302
1
+ trilogy/__init__.py,sha256=vUdxo3nPFBARzvUHLzXRZLQKmcfdfzLaKOQTAayfTYE,303
2
2
  trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  trilogy/constants.py,sha256=qZ1d0hoKPPV2HHCoFwPYTVB7b6bXjpWvXd3lE-zEhy8,1494
4
4
  trilogy/engine.py,sha256=3etkm2RSVKO0IkgPKkrcs33X5gN_fIMyqMNfChcsR1E,1318
5
- trilogy/executor.py,sha256=YgSCeeYVecI9526LGSLVe2apOo7Ddsttvs_nDC9yElQ,17194
5
+ trilogy/executor.py,sha256=9HhdLQoou1Cy9KSDgpdYxK6uyg-UPkgx9jXJBuK5ITc,17271
6
6
  trilogy/parser.py,sha256=o4cfk3j3yhUFoiDKq9ZX_GjBF3dKhDjXEwb63rcBkBM,293
7
7
  trilogy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  trilogy/utility.py,sha256=euQccZLKoYBz0LNg5tzLlvv2YHvXh9HArnYp1V3uXsM,763
9
- trilogy/authoring/__init__.py,sha256=6eIJewNIbrMdP6_dyLpyZ0uU62_pSj9wCP80Pkz2Bl8,1922
9
+ trilogy/authoring/__init__.py,sha256=fP4oIRG41yW-gqpMjIOL0jBffh6Wlw8nR3HXhaPkKxc,2066
10
10
  trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  trilogy/core/constants.py,sha256=7XaCpZn5mQmjTobbeBn56SzPWq9eMNDfzfsRU-fP0VE,171
12
- trilogy/core/enums.py,sha256=aTFXL6nckmG0hpNLdCxI2kAT26cIsZPahF-pHzLQ9uc,7085
12
+ trilogy/core/enums.py,sha256=jOgUyoLGgC5-8SLBWP0jQDMxptBJbN0J48VsCa1EMYo,7111
13
13
  trilogy/core/env_processor.py,sha256=pFsxnluKIusGKx1z7tTnfsd_xZcPy9pZDungkjkyvI0,3170
14
14
  trilogy/core/environment_helpers.py,sha256=oOpewPwMp8xOtx2ayeeyuLNUwr-cli7UanHKot5ebNY,7627
15
15
  trilogy/core/ergonomics.py,sha256=ASLDd0RqKWrZiG3XcKHo8nyTjaB_8xfE9t4NZ1UvGpc,1639
16
16
  trilogy/core/exceptions.py,sha256=JPYyBcit3T_pRtlHdtKSeVJkIyWUTozW2aaut25A2xI,673
17
- trilogy/core/functions.py,sha256=rIkZGzw9hpIkXvuqQ1qPWFJO1W_NPYc6T9t0wTZ55M0,24784
17
+ trilogy/core/functions.py,sha256=EsRYHE2kg_FckceVgYGPPs1ylrXvBjr3l1Wa6r0SvL8,25027
18
18
  trilogy/core/graph_models.py,sha256=z17EoO8oky2QOuO6E2aMWoVNKEVJFhLdsQZOhC4fNLU,2079
19
19
  trilogy/core/internal.py,sha256=iicDBlC6nM8d7e7jqzf_ZOmpUsW8yrr2AA8AqEiLx-s,1577
20
20
  trilogy/core/optimization.py,sha256=xGO8piVsLrpqrx-Aid_Y56_5slSv4eZmlP64hCHRiEc,7957
21
21
  trilogy/core/query_processor.py,sha256=Do8YpdPBdsbKtl9n37hobzk8SORMGqH-e_zNNxd-BE4,19456
22
22
  trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- trilogy/core/models/author.py,sha256=S6riJx8Z4_orbohjK3fcZh5Ei6nCPYAS2FGDsI2njHk,69488
24
- trilogy/core/models/build.py,sha256=z2QO7l2E2-1hHimmOGsLl42sTnEb2x9o45zkvOoJYpM,56687
23
+ trilogy/core/models/author.py,sha256=CHVnj-GW_gjGDHaAUg60VGOfPSRr_5n5LbHvocOhQj8,70371
24
+ trilogy/core/models/build.py,sha256=bO1qYvuGl6LeNGgsfS6ZHAzZBR2lBPLg-QJymp9hgkU,57235
25
25
  trilogy/core/models/build_environment.py,sha256=8UggvlPU708GZWYPJMc_ou2r7M3TY2g69eqGvz03YX0,5528
26
- trilogy/core/models/core.py,sha256=yie1uuq62uOQ5fjob9NMJbdvQPrCErXUT7JTCuYRyjI,9697
26
+ trilogy/core/models/core.py,sha256=00opIUXwgJy9OF-cwI883zQpArNeh6wkTpSqUszDU78,9909
27
27
  trilogy/core/models/datasource.py,sha256=6RjJUd2u4nYmEwFBpJlM9LbHVYDv8iHJxqiBMZqUrwI,9422
28
- trilogy/core/models/environment.py,sha256=qFZ0_Op6zIhKc5oVS4EVYZ67f29wJhKP_xoEMV4kkuU,25991
28
+ trilogy/core/models/environment.py,sha256=BpmHeSfIz_uNlx4IJ7LnDbQrQwEjNU7QoYcJn0uhpeo,26009
29
29
  trilogy/core/models/execute.py,sha256=ABylFQgtavjjCfFkEsFdUwfMB4UBQLHjdzQ9E67QlAE,33521
30
30
  trilogy/core/optimizations/__init__.py,sha256=EBanqTXEzf1ZEYjAneIWoIcxtMDite5-n2dQ5xcfUtg,356
31
31
  trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnbnb6vdVIztUfFQZsGnM,513
@@ -63,12 +63,12 @@ trilogy/core/processing/nodes/union_node.py,sha256=fDFzLAUh5876X6_NM7nkhoMvHEdGJ
63
63
  trilogy/core/processing/nodes/unnest_node.py,sha256=oLKMMNMx6PLDPlt2V5neFMFrFWxET8r6XZElAhSNkO0,2181
64
64
  trilogy/core/processing/nodes/window_node.py,sha256=STvwheVttxSWVHB-yUQUSo-Pyz7Uk8G1txFDAbWMp-s,1380
65
65
  trilogy/core/statements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
66
- trilogy/core/statements/author.py,sha256=9wKZDwQ-BeaUCMjD9l0ffMMv8zivaYcAg12UhVFi-0Y,14248
66
+ trilogy/core/statements/author.py,sha256=X3NdGlWTUCNdQQw9EGI1kccgsLJMazwNAIsqpUSr_ZY,14443
67
67
  trilogy/core/statements/build.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
68
  trilogy/core/statements/common.py,sha256=KxEmz2ySySyZ6CTPzn0fJl5NX2KOk1RPyuUSwWhnK1g,759
69
69
  trilogy/core/statements/execute.py,sha256=cSlvpHFOqpiZ89pPZ5GDp9Hu6j6uj-5_h21FWm_L-KM,1248
70
70
  trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
- trilogy/dialect/base.py,sha256=u00kIIl98as1QzcduiiyyoBzxRGVeBxfeO5hWlRCAJU,40222
71
+ trilogy/dialect/base.py,sha256=gEbbqll98nfhxs_JWK0h_9M9fh4nWnon2Ntlxp_s_lI,40287
72
72
  trilogy/dialect/bigquery.py,sha256=mKC3zoEU232h9RtIXJjqiZ72lWH8a6S28p6wAZKrAfg,2952
73
73
  trilogy/dialect/common.py,sha256=cbTo_vamdp8pj9spSjGSH-bSZpy4FpNJ12k5vMvyT2Y,3942
74
74
  trilogy/dialect/config.py,sha256=EGYRQIbrkeMuud5Bkds7jSD5dCJR5hEYZUYcy-lYZl4,3308
@@ -85,18 +85,18 @@ trilogy/hooks/graph_hook.py,sha256=c-vC-IXoJ_jDmKQjxQyIxyXPOuUcLIURB573gCsAfzQ,2
85
85
  trilogy/hooks/query_debugger.py,sha256=1npRjww94sPV5RRBBlLqMJRaFkH9vhEY6o828MeoEcw,5583
86
86
  trilogy/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
87
87
  trilogy/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
88
- trilogy/parsing/common.py,sha256=RwO9CdNYy3KxJCjg5Ta_xJwfZHV2PuRErxg66Cs50ws,20490
88
+ trilogy/parsing/common.py,sha256=IgZ2K3LzJ0usLIwxRCRmS-4luP6uwmM-f1oqGNyGbm0,21306
89
89
  trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
90
90
  trilogy/parsing/exceptions.py,sha256=92E5i2frv5hj9wxObJZsZqj5T6bglvPzvdvco_vW1Zk,38
91
91
  trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
92
- trilogy/parsing/parse_engine.py,sha256=uZ6MYjg6kkTm5HFfOKLGvVvzHiGgH-vY7lV-AIlIBgY,55701
92
+ trilogy/parsing/parse_engine.py,sha256=MiH5Kw6U512oseE-7XMyDUJRmUtUqCCzjS8MKsywNoQ,56375
93
93
  trilogy/parsing/render.py,sha256=o_XuQWhcwx1lD9eGVqkqZEwkmQK0HdmWWokGBtdeH4I,17837
94
- trilogy/parsing/trilogy.lark,sha256=wZpqI1louDqm-t-TpmzW749dPA9w2EIAyowyEJIeXAM,12620
94
+ trilogy/parsing/trilogy.lark,sha256=zehaPaYKuJZQ335sgCjH8Q6u_hy5A6A02XcdwziZdWE,12817
95
95
  trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
96
96
  trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
97
- pytrilogy-0.0.3.8.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
98
- pytrilogy-0.0.3.8.dist-info/METADATA,sha256=DAu0XOCyEgXpZj9-Znl0IbtouGXHELjt2EUZnp3IgEs,8983
99
- pytrilogy-0.0.3.8.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
100
- pytrilogy-0.0.3.8.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
101
- pytrilogy-0.0.3.8.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
102
- pytrilogy-0.0.3.8.dist-info/RECORD,,
97
+ pytrilogy-0.0.3.10.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
98
+ pytrilogy-0.0.3.10.dist-info/METADATA,sha256=LpLZFxaYTstBO1oc2-pil2pQNH-P7hrTkOQ35PWPm8A,8984
99
+ pytrilogy-0.0.3.10.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
100
+ pytrilogy-0.0.3.10.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
101
+ pytrilogy-0.0.3.10.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
102
+ pytrilogy-0.0.3.10.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.8"
7
+ __version__ = "0.0.3.10"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -38,6 +38,9 @@ from trilogy.core.models.environment import Environment
38
38
  from trilogy.core.statements.author import (
39
39
  ConceptDeclarationStatement,
40
40
  ConceptTransform,
41
+ MultiSelectStatement,
42
+ PersistStatement,
43
+ RawSQLStatement,
41
44
  SelectItem,
42
45
  SelectStatement,
43
46
  )
@@ -87,4 +90,7 @@ __all__ = [
87
90
  "DEFAULT_NAMESPACE",
88
91
  "arbitrary_to_concept",
89
92
  "arg_to_datatype",
93
+ "MultiSelectStatement",
94
+ "PersistStatement",
95
+ "RawSQLStatement",
90
96
  ]
trilogy/core/enums.py CHANGED
@@ -172,6 +172,7 @@ class FunctionType(Enum):
172
172
  UPPER = "upper"
173
173
  SUBSTRING = "substring"
174
174
  STRPOS = "strpos"
175
+ CONTAINS = "contains"
175
176
 
176
177
  # Dates
177
178
  DATE = "date"
trilogy/core/functions.py CHANGED
@@ -271,6 +271,15 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
271
271
  output_type=DataType.INTEGER,
272
272
  arg_count=2,
273
273
  ),
274
+ FunctionType.CONTAINS: FunctionConfig(
275
+ valid_inputs=[
276
+ {DataType.STRING},
277
+ {DataType.STRING},
278
+ ],
279
+ output_purpose=Purpose.PROPERTY,
280
+ output_type=DataType.BOOL,
281
+ arg_count=2,
282
+ ),
274
283
  FunctionType.SUBSTRING: FunctionConfig(
275
284
  valid_inputs=[{DataType.STRING}, {DataType.INTEGER}, {DataType.INTEGER}],
276
285
  output_purpose=Purpose.PROPERTY,
@@ -55,6 +55,7 @@ from trilogy.core.models.core import (
55
55
  MapWrapper,
56
56
  NumericType,
57
57
  StructType,
58
+ TraitDataType,
58
59
  TupleWrapper,
59
60
  arg_to_datatype,
60
61
  is_compatible_datatype,
@@ -101,9 +102,9 @@ class HasUUID(ABC):
101
102
 
102
103
  class ConceptRef(Addressable, Namespaced, DataTyped, Mergeable, BaseModel):
103
104
  address: str
104
- datatype: DataType | ListType | StructType | MapType | NumericType = (
105
- DataType.UNKNOWN
106
- )
105
+ datatype: (
106
+ DataType | TraitDataType | ListType | StructType | MapType | NumericType
107
+ ) = DataType.UNKNOWN
107
108
  metadata: Optional["Metadata"] = None
108
109
 
109
110
  @property
@@ -547,6 +548,7 @@ class Comparison(ConceptArgs, Mergeable, Namespaced, BaseModel):
547
548
  MagicConstants,
548
549
  WindowItem,
549
550
  AggregateWrapper,
551
+ FilterItem,
550
552
  ]
551
553
  right: Union[
552
554
  int,
@@ -566,6 +568,7 @@ class Comparison(ConceptArgs, Mergeable, Namespaced, BaseModel):
566
568
  WindowItem,
567
569
  AggregateWrapper,
568
570
  TupleWrapper,
571
+ FilterItem,
569
572
  ]
570
573
  operator: ComparisonOperator
571
574
 
@@ -754,7 +757,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
754
757
  extra="forbid",
755
758
  )
756
759
  name: str
757
- datatype: DataType | ListType | StructType | MapType | NumericType
760
+ datatype: DataType | TraitDataType | ListType | StructType | MapType | NumericType
758
761
  purpose: Purpose
759
762
  derivation: Derivation = Derivation.ROOT
760
763
  granularity: Granularity = Granularity.MULTI_ROW
@@ -1215,9 +1218,9 @@ class UndefinedConceptFull(Concept, Mergeable, Namespaced):
1215
1218
  model_config = ConfigDict(arbitrary_types_allowed=True)
1216
1219
  name: str
1217
1220
  line_no: int | None = None
1218
- datatype: DataType | ListType | StructType | MapType | NumericType = (
1219
- DataType.UNKNOWN
1220
- )
1221
+ datatype: (
1222
+ DataType | TraitDataType | ListType | StructType | MapType | NumericType
1223
+ ) = DataType.UNKNOWN
1221
1224
  purpose: Purpose = Purpose.UNKNOWN
1222
1225
 
1223
1226
  @property
@@ -1249,7 +1252,7 @@ class OrderItem(Mergeable, ConceptArgs, Namespaced, BaseModel):
1249
1252
  def with_merge(
1250
1253
  self, source: Concept, target: Concept, modifiers: List[Modifier]
1251
1254
  ) -> "OrderItem":
1252
- return OrderItem(
1255
+ return OrderItem.model_construct(
1253
1256
  expr=(
1254
1257
  self.expr.with_merge(source, target, modifiers)
1255
1258
  if isinstance(self.expr, Mergeable)
@@ -1258,6 +1261,16 @@ class OrderItem(Mergeable, ConceptArgs, Namespaced, BaseModel):
1258
1261
  order=self.order,
1259
1262
  )
1260
1263
 
1264
+ def with_reference_replacement(self, source, target):
1265
+ return OrderItem.model_construct(
1266
+ expr=(
1267
+ self.expr.with_reference_replacement(source, target)
1268
+ if isinstance(self.expr, Mergeable)
1269
+ else self.expr
1270
+ ),
1271
+ order=self.order,
1272
+ )
1273
+
1261
1274
  @property
1262
1275
  def concept_arguments(self) -> Sequence[ConceptRef]:
1263
1276
  return get_concept_arguments(self.expr)
@@ -1320,6 +1333,17 @@ class WindowItem(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1320
1333
  )
1321
1334
  return output
1322
1335
 
1336
+ def with_reference_replacement(self, source, target):
1337
+ return WindowItem.model_construct(
1338
+ type=self.type,
1339
+ content=self.content.with_reference_replacement(source, target),
1340
+ over=[x.with_reference_replacement(source, target) for x in self.over],
1341
+ order_by=[
1342
+ x.with_reference_replacement(source, target) for x in self.order_by
1343
+ ],
1344
+ index=self.index,
1345
+ )
1346
+
1323
1347
  def with_namespace(self, namespace: str) -> "WindowItem":
1324
1348
  return WindowItem.model_construct(
1325
1349
  type=self.type,
@@ -1515,30 +1539,7 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1515
1539
  List[Set[DataType]],
1516
1540
  ]
1517
1541
  ] = None
1518
- arguments: Sequence[
1519
- Union[
1520
- ConceptRef,
1521
- AggregateWrapper,
1522
- Function,
1523
- Parenthetical,
1524
- CaseWhen,
1525
- CaseElse,
1526
- WindowItem,
1527
- int,
1528
- float,
1529
- str,
1530
- date,
1531
- datetime,
1532
- MapWrapper[Any, Any],
1533
- DataType,
1534
- ListType,
1535
- MapType,
1536
- NumericType,
1537
- DatePart,
1538
- list,
1539
- ListWrapper[Any],
1540
- ]
1541
- ]
1542
+ arguments: Sequence[FuncArgs]
1542
1543
 
1543
1544
  def __init__(self, **kwargs):
1544
1545
  super().__init__(**kwargs)
@@ -2153,6 +2154,11 @@ class ArgBinding(BaseModel):
2153
2154
  default: Expr | None = None
2154
2155
 
2155
2156
 
2157
+ class CustomType(BaseModel):
2158
+ name: str
2159
+ type: DataType
2160
+
2161
+
2156
2162
  Expr = (
2157
2163
  MagicConstants
2158
2164
  | bool
@@ -2164,6 +2170,7 @@ Expr = (
2164
2170
  | datetime
2165
2171
  | TupleWrapper
2166
2172
  | ListWrapper
2173
+ | MapWrapper
2167
2174
  | WindowItem
2168
2175
  | FilterItem
2169
2176
  | ConceptRef
@@ -2172,4 +2179,30 @@ Expr = (
2172
2179
  | Parenthetical
2173
2180
  | Function
2174
2181
  | AggregateWrapper
2182
+ | CaseWhen
2183
+ | CaseElse
2184
+ )
2185
+
2186
+ FuncArgs = (
2187
+ ConceptRef
2188
+ | AggregateWrapper
2189
+ | Function
2190
+ | Parenthetical
2191
+ | CaseWhen
2192
+ | CaseElse
2193
+ | WindowItem
2194
+ | FilterItem
2195
+ | int
2196
+ | float
2197
+ | str
2198
+ | date
2199
+ | datetime
2200
+ | MapWrapper[Any, Any]
2201
+ | DataType
2202
+ | ListType
2203
+ | MapType
2204
+ | NumericType
2205
+ | DatePart
2206
+ | list
2207
+ | ListWrapper[Any]
2175
2208
  )
@@ -51,6 +51,7 @@ from trilogy.core.models.author import (
51
51
  ConceptRef,
52
52
  Conditional,
53
53
  FilterItem,
54
+ FuncArgs,
54
55
  Function,
55
56
  Grain,
56
57
  HavingClause,
@@ -1498,10 +1499,22 @@ class Factory:
1498
1499
 
1499
1500
  @build.register
1500
1501
  def _(self, base: Function) -> BuildFunction:
1502
+ from trilogy.parsing.common import arbitrary_to_concept
1501
1503
 
1504
+ raw_args: list[Concept | FuncArgs] = []
1505
+ for arg in base.arguments:
1506
+ # to do proper discovery, we need to inject virtual intermediate ocncepts
1507
+ if isinstance(arg, (AggregateWrapper, FilterItem, WindowItem)):
1508
+ narg = arbitrary_to_concept(
1509
+ arg,
1510
+ environment=self.environment,
1511
+ )
1512
+ raw_args.append(narg)
1513
+ else:
1514
+ raw_args.append(arg)
1502
1515
  new = BuildFunction.model_construct(
1503
1516
  operator=base.operator,
1504
- arguments=[self.build(c) for c in base.arguments],
1517
+ arguments=[self.build(c) for c in raw_args],
1505
1518
  output_datatype=base.output_datatype,
1506
1519
  output_purpose=base.output_purpose,
1507
1520
  valid_inputs=base.valid_inputs,
@@ -97,6 +97,19 @@ class DataType(Enum):
97
97
  return self
98
98
 
99
99
 
100
+ class TraitDataType(BaseModel):
101
+ type: DataType
102
+ traits: list[str]
103
+
104
+ @property
105
+ def data_type(self):
106
+ return self.type
107
+
108
+ @property
109
+ def value(self):
110
+ return self.data_type.value
111
+
112
+
100
113
  class NumericType(BaseModel):
101
114
  precision: int = 20
102
115
  scale: int = 5
@@ -40,6 +40,7 @@ from trilogy.core.exceptions import (
40
40
  from trilogy.core.models.author import (
41
41
  Concept,
42
42
  ConceptRef,
43
+ CustomType,
43
44
  Function,
44
45
  SelectLineage,
45
46
  UndefinedConcept,
@@ -191,7 +192,7 @@ class Environment(BaseModel):
191
192
  EnvironmentDatasourceDict, PlainValidator(validate_datasources)
192
193
  ] = Field(default_factory=EnvironmentDatasourceDict)
193
194
  functions: Dict[str, Callable[..., Any]] = Field(default_factory=dict)
194
- data_types: Dict[str, DataType] = Field(default_factory=dict)
195
+ data_types: Dict[str, CustomType] = Field(default_factory=dict)
195
196
  named_statements: Dict[str, SelectLineage] = Field(default_factory=dict)
196
197
  imports: Dict[str, list[Import]] = Field(
197
198
  default_factory=lambda: defaultdict(list) # type: ignore
@@ -15,8 +15,11 @@ from trilogy.core.enums import (
15
15
  from trilogy.core.models.author import (
16
16
  AggregateWrapper,
17
17
  AlignClause,
18
+ ArgBinding,
18
19
  Concept,
19
20
  ConceptRef,
21
+ CustomType,
22
+ Expr,
20
23
  FilterItem,
21
24
  Function,
22
25
  Grain,
@@ -413,3 +416,13 @@ class ConceptDeclarationStatement(HasUUID, BaseModel):
413
416
 
414
417
  class ConceptDerivationStatement(BaseModel):
415
418
  concept: Concept
419
+
420
+
421
+ class TypeDeclaration(BaseModel):
422
+ type: CustomType
423
+
424
+
425
+ class FunctionDeclaration(BaseModel):
426
+ name: str
427
+ args: list[ArgBinding]
428
+ expr: Expr
trilogy/dialect/base.py CHANGED
@@ -184,6 +184,7 @@ FUNCTION_MAP = {
184
184
  FunctionType.LOWER: lambda x: f"LOWER({x[0]}) ",
185
185
  FunctionType.SUBSTRING: lambda x: f"SUBSTRING({x[0]},{x[1]},{x[2]})",
186
186
  FunctionType.STRPOS: lambda x: f"STRPOS({x[0]},{x[1]})",
187
+ FunctionType.CONTAINS: lambda x: f"CONTAINS({x[0]},{x[1]})",
187
188
  # FunctionType.NOT_LIKE: lambda x: f" CASE WHEN {x[0]} like {x[1]} THEN 0 ELSE 1 END",
188
189
  # date types
189
190
  FunctionType.DATE_TRUNCATE: lambda x: f"date_trunc({x[0]},{x[1]})",
trilogy/executor.py CHANGED
@@ -166,8 +166,11 @@ class Executor(object):
166
166
  )
167
167
 
168
168
  @execute_query.register
169
- def _(self, query: str) -> CursorResult:
170
- return self.execute_text(query)[-1]
169
+ def _(self, query: str) -> CursorResult | None:
170
+ results = self.execute_text(query)
171
+ if results:
172
+ return results[-1]
173
+ return None
171
174
 
172
175
  @execute_query.register
173
176
  def _(self, query: SelectStatement) -> CursorResult:
trilogy/parsing/common.py CHANGED
@@ -84,7 +84,8 @@ def process_function_arg(
84
84
  environment.add_concept(concept, meta=meta)
85
85
  return concept
86
86
  elif isinstance(
87
- arg, (FilterItem, WindowItem, AggregateWrapper, ListWrapper, MapWrapper)
87
+ arg,
88
+ (ListWrapper, MapWrapper),
88
89
  ):
89
90
  id_hash = string_to_hash(str(arg))
90
91
  name = f"{VIRTUAL_CONCEPT_PREFIX}_{id_hash}"
@@ -249,6 +250,24 @@ def concepts_to_grain_concepts(
249
250
  return v2
250
251
 
251
252
 
253
+ def get_relevant_parent_concepts(arg) -> tuple[list[ConceptRef], bool]:
254
+ from trilogy.core.models.author import get_concept_arguments
255
+
256
+ is_metric = False
257
+ if isinstance(arg, Function):
258
+ all = []
259
+ for y in arg.arguments:
260
+ refs, local_flag = get_relevant_parent_concepts(y)
261
+ all += refs
262
+ is_metric = is_metric or local_flag
263
+ return all, is_metric
264
+ elif isinstance(arg, AggregateWrapper) and not arg.by:
265
+ return [], True
266
+ elif isinstance(arg, AggregateWrapper) and arg.by:
267
+ return arg.by, True
268
+ return get_concept_arguments(arg), False
269
+
270
+
252
271
  def function_to_concept(
253
272
  parent: Function,
254
273
  name: str,
@@ -256,14 +275,12 @@ def function_to_concept(
256
275
  namespace: str | None = None,
257
276
  metadata: Metadata | None = None,
258
277
  ) -> Concept:
278
+
259
279
  pkeys: List[Concept] = []
260
280
  namespace = namespace or environment.namespace
261
- concrete_args = [
262
- x
263
- for x in [environment.concepts[c.address] for c in parent.concept_arguments]
264
- if not isinstance(x, UndefinedConcept)
265
- ]
266
-
281
+ is_metric = False
282
+ ref_args, is_metric = get_relevant_parent_concepts(parent)
283
+ concrete_args = [environment.concepts[c.address] for c in ref_args]
267
284
  pkeys += [x for x in concrete_args if not x.derivation == Derivation.CONSTANT]
268
285
  grain: Grain | None = Grain()
269
286
  for x in pkeys:
@@ -274,12 +291,18 @@ def function_to_concept(
274
291
  modifiers = get_upstream_modifiers(pkeys, environment)
275
292
  key_grain: list[str] = []
276
293
  for x in pkeys:
277
- if x.keys:
294
+ # metrics will group to keys, so do no do key traversal
295
+ if is_metric:
296
+ key_grain.append(x.address)
297
+ # otherwse, for row ops, assume keys are transitive
298
+ elif x.keys:
278
299
  key_grain += [*x.keys]
279
300
  else:
280
301
  key_grain.append(x.address)
281
302
  keys = set(key_grain)
282
- if not pkeys:
303
+ if is_metric:
304
+ purpose = Purpose.METRIC
305
+ elif not pkeys:
283
306
  purpose = Purpose.CONSTANT
284
307
  else:
285
308
  purpose = parent.output_purpose
@@ -4,7 +4,7 @@ from enum import Enum
4
4
  from os.path import dirname, join
5
5
  from pathlib import Path
6
6
  from re import IGNORECASE
7
- from typing import Callable, List, Optional, Tuple, Union
7
+ from typing import List, Optional, Tuple, Union
8
8
 
9
9
  from lark import Lark, ParseTree, Transformer, Tree, v_args
10
10
  from lark.exceptions import (
@@ -57,6 +57,7 @@ from trilogy.core.models.author import (
57
57
  Concept,
58
58
  ConceptRef,
59
59
  Conditional,
60
+ CustomType,
60
61
  Expr,
61
62
  FilterItem,
62
63
  Function,
@@ -82,6 +83,7 @@ from trilogy.core.models.core import (
82
83
  MapWrapper,
83
84
  NumericType,
84
85
  StructType,
86
+ TraitDataType,
85
87
  TupleWrapper,
86
88
  arg_to_datatype,
87
89
  dict_to_map_wrapper,
@@ -101,6 +103,7 @@ from trilogy.core.statements.author import (
101
103
  ConceptDerivationStatement,
102
104
  ConceptTransform,
103
105
  CopyStatement,
106
+ FunctionDeclaration,
104
107
  ImportStatement,
105
108
  Limit,
106
109
  MergeStatementV2,
@@ -111,6 +114,7 @@ from trilogy.core.statements.author import (
111
114
  SelectItem,
112
115
  SelectStatement,
113
116
  ShowStatement,
117
+ TypeDeclaration,
114
118
  )
115
119
  from trilogy.parsing.common import (
116
120
  align_item_to_concept,
@@ -365,8 +369,9 @@ class ParseToObjects(Transformer):
365
369
 
366
370
  def data_type(
367
371
  self, args
368
- ) -> DataType | ListType | StructType | MapType | NumericType:
372
+ ) -> DataType | TraitDataType | ListType | StructType | MapType | NumericType:
369
373
  resolved = args[0]
374
+ traits = args[2:]
370
375
  if isinstance(resolved, StructType):
371
376
  return resolved
372
377
  elif isinstance(resolved, ListType):
@@ -375,7 +380,10 @@ class ParseToObjects(Transformer):
375
380
  return resolved
376
381
  elif isinstance(resolved, MapType):
377
382
  return resolved
378
- return DataType(args[0].lower())
383
+ base = DataType(args[0].lower())
384
+ if traits:
385
+ return TraitDataType(type=base, traits=traits)
386
+ return base
379
387
 
380
388
  def array_comparison(self, args) -> ComparisonOperator:
381
389
  return ComparisonOperator([x.value.lower() for x in args])
@@ -1087,7 +1095,7 @@ class ParseToObjects(Transformer):
1087
1095
  return ArgBinding(name=args[0], default=None)
1088
1096
 
1089
1097
  @v_args(meta=True)
1090
- def raw_function(self, meta: Meta, args) -> Callable[[list[Expr]], Expr]:
1098
+ def raw_function(self, meta: Meta, args) -> FunctionDeclaration:
1091
1099
  identity = args[0]
1092
1100
  function_arguments: list[ArgBinding] = args[1]
1093
1101
  output = args[2]
@@ -1109,7 +1117,7 @@ class ParseToObjects(Transformer):
1109
1117
  return nout
1110
1118
 
1111
1119
  self.environment.functions[identity] = function_factory
1112
- return function_factory
1120
+ return FunctionDeclaration(name=identity, args=function_arguments, expr=output)
1113
1121
 
1114
1122
  def custom_function(self, args):
1115
1123
  name = args[0]
@@ -1121,6 +1129,13 @@ class ParseToObjects(Transformer):
1121
1129
  def function(self, meta: Meta, args) -> Function:
1122
1130
  return args[0]
1123
1131
 
1132
+ @v_args(meta=True)
1133
+ def type_declaration(self, meta: Meta, args) -> TypeDeclaration:
1134
+ key = args[0]
1135
+ datatype = args[1]
1136
+ self.environment.data_types[key] = datatype
1137
+ return TypeDeclaration(type=CustomType(name=key, type=datatype))
1138
+
1124
1139
  def int_lit(self, args):
1125
1140
  return int("".join(args))
1126
1141
 
@@ -1441,6 +1456,10 @@ class ParseToObjects(Transformer):
1441
1456
  def fstrpos(self, meta, args):
1442
1457
  return self.function_factory.create_function(args, FunctionType.STRPOS, meta)
1443
1458
 
1459
+ @v_args(meta=True)
1460
+ def fcontains(self, meta, args):
1461
+ return self.function_factory.create_function(args, FunctionType.CONTAINS, meta)
1462
+
1444
1463
  @v_args(meta=True)
1445
1464
  def fsubstring(self, meta, args):
1446
1465
  return self.function_factory.create_function(args, FunctionType.SUBSTRING, meta)
@@ -3,6 +3,7 @@
3
3
  ?statement: concept
4
4
  | datasource
5
5
  | function
6
+ | type_declaration
6
7
  | multi_select_statement
7
8
  | select_statement
8
9
  | persist_statement
@@ -92,14 +93,15 @@
92
93
  function_binding_item: IDENTIFIER ("=" literal)?
93
94
  function_binding_list: (function_binding_item ",")* function_binding_item ","?
94
95
  raw_function: "def" IDENTIFIER "(" function_binding_list ")" "->" expr
95
-
96
+
97
+ // TYPE blocks
98
+ type_declaration: "type" IDENTIFIER data_type
99
+
96
100
  // user_id where state = Mexico
97
101
  _filter_alt: IDENTIFIER "?" conditional
98
102
  _filter_base: "filter"i IDENTIFIER where
99
103
  filter_item: _filter_base | _filter_alt
100
104
 
101
-
102
-
103
105
  // rank/lag/lead
104
106
  WINDOW_TYPE: ("row_number"i|"rank"i|"lag"i|"lead"i | "sum"i | "avg"i | "max"i | "min"i ) /[\s]+/
105
107
 
@@ -223,16 +225,18 @@
223
225
  alt_like: expr "like"i expr
224
226
  _UPPER.1: "upper("i
225
227
  upper: _UPPER expr ")"
226
- _LOWER.1: "lower("i
228
+ _LOWER.1: "lower("i
227
229
  lower: _LOWER expr ")"
228
230
  _SPLIT.1: "split("i
229
231
  fsplit: _SPLIT expr "," string_lit ")"
230
- _STRPOS.1: "strpos("i
232
+ _STRPOS.1: "strpos("i
231
233
  fstrpos: _STRPOS expr "," expr ")"
234
+ _CONTAINS.1: "contains("i
235
+ fcontains: _CONTAINS expr "," expr ")"
232
236
  _SUBSTRING.1: "substring("i
233
237
  fsubstring: _SUBSTRING expr "," expr "," expr ")"
234
238
 
235
- _string_functions: like | ilike | upper | lower | fsplit | fstrpos | fsubstring
239
+ _string_functions: like | ilike | upper | lower | fsplit | fstrpos | fsubstring | fcontains
236
240
 
237
241
  // special aggregate
238
242
  _GROUP.1: "group("i
@@ -348,7 +352,7 @@
348
352
 
349
353
  map_type: "map"i "<" data_type "," data_type ">"
350
354
 
351
- !data_type: "string"i | "number"i | "numeric"i | "map"i | "list"i | "array"i | "any"i | "int"i | "bigint"i | "date"i | "datetime"i | "timestamp"i | "float"i | "bool"i | numeric_type | map_type | struct_type | list_type
355
+ !data_type: ("string"i | "number"i | "numeric"i | "map"i | "list"i | "array"i | "any"i | "int"i | "bigint"i | "date"i | "datetime"i | "timestamp"i | "float"i | "bool"i | numeric_type | map_type | struct_type | list_type) ("::" IDENTIFIER)?
352
356
 
353
357
  PURPOSE: "key"i | "metric"i | CONST
354
358
  PROPERTY: "property"i
@@ -361,7 +365,7 @@
361
365
  show_category: CONCEPTS | DATASOURCES
362
366
 
363
367
  show_statement: "show"i ( show_category | select_statement | persist_statement) _TERMINATOR
364
- COMMENT: /#.*(\n|$)/ | /\/\/.*\n/
368
+ COMMENT: /#.*(\n|$)/ | /\/\/.*\n/
365
369
  %import common.WS
366
370
  %ignore WS
367
371
  %ignore COMMENT