pytrilogy 0.0.3.7__py3-none-any.whl → 0.0.3.8__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.7.dist-info → pytrilogy-0.0.3.8.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.3.7.dist-info → pytrilogy-0.0.3.8.dist-info}/RECORD +18 -18
- trilogy/__init__.py +1 -1
- trilogy/core/models/author.py +104 -39
- trilogy/core/models/build.py +20 -3
- trilogy/core/models/environment.py +2 -1
- trilogy/core/processing/node_generators/group_node.py +1 -1
- trilogy/core/processing/node_generators/multiselect_node.py +1 -1
- trilogy/core/statements/author.py +2 -0
- trilogy/dialect/config.py +8 -2
- trilogy/dialect/dataframe.py +9 -4
- trilogy/parsing/common.py +57 -48
- trilogy/parsing/parse_engine.py +53 -33
- trilogy/parsing/trilogy.lark +6 -4
- {pytrilogy-0.0.3.7.dist-info → pytrilogy-0.0.3.8.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.3.7.dist-info → pytrilogy-0.0.3.8.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.3.7.dist-info → pytrilogy-0.0.3.8.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.3.7.dist-info → pytrilogy-0.0.3.8.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
trilogy/__init__.py,sha256=
|
|
1
|
+
trilogy/__init__.py,sha256=OtihFBeuWR7p7GBhiNMfyCpmu-x2kn_W2xSawos5ILM,302
|
|
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
|
|
@@ -20,12 +20,12 @@ 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=
|
|
24
|
-
trilogy/core/models/build.py,sha256=
|
|
23
|
+
trilogy/core/models/author.py,sha256=S6riJx8Z4_orbohjK3fcZh5Ei6nCPYAS2FGDsI2njHk,69488
|
|
24
|
+
trilogy/core/models/build.py,sha256=z2QO7l2E2-1hHimmOGsLl42sTnEb2x9o45zkvOoJYpM,56687
|
|
25
25
|
trilogy/core/models/build_environment.py,sha256=8UggvlPU708GZWYPJMc_ou2r7M3TY2g69eqGvz03YX0,5528
|
|
26
26
|
trilogy/core/models/core.py,sha256=yie1uuq62uOQ5fjob9NMJbdvQPrCErXUT7JTCuYRyjI,9697
|
|
27
27
|
trilogy/core/models/datasource.py,sha256=6RjJUd2u4nYmEwFBpJlM9LbHVYDv8iHJxqiBMZqUrwI,9422
|
|
28
|
-
trilogy/core/models/environment.py,sha256=
|
|
28
|
+
trilogy/core/models/environment.py,sha256=qFZ0_Op6zIhKc5oVS4EVYZ67f29wJhKP_xoEMV4kkuU,25991
|
|
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
|
|
@@ -40,9 +40,9 @@ trilogy/core/processing/node_generators/__init__.py,sha256=o8rOFHPSo-s_59hREwXMW
|
|
|
40
40
|
trilogy/core/processing/node_generators/basic_node.py,sha256=UVsXMn6jTjm_ofVFt218jAS11s4RV4zD781vP4im-GI,3371
|
|
41
41
|
trilogy/core/processing/node_generators/common.py,sha256=ZsDzThjm_mAtdQpKAg8QIJiPVZ4KuUkKyilj4eOhSDs,9439
|
|
42
42
|
trilogy/core/processing/node_generators/filter_node.py,sha256=rlY7TbgjJlGhahYgdCIJpJbaSREAGVJEsyUIGaA38O0,8271
|
|
43
|
-
trilogy/core/processing/node_generators/group_node.py,sha256=
|
|
43
|
+
trilogy/core/processing/node_generators/group_node.py,sha256=3-TXVnRO9_jqE_e1kWLqbgtBShW8WFtKwQk8oOtOULs,5894
|
|
44
44
|
trilogy/core/processing/node_generators/group_to_node.py,sha256=E5bEjovSx422d_MlAUCDFdY4P2WJVp61BmWwltkhzA8,3095
|
|
45
|
-
trilogy/core/processing/node_generators/multiselect_node.py,sha256=
|
|
45
|
+
trilogy/core/processing/node_generators/multiselect_node.py,sha256=GWV5yLmKTe1yyPhN60RG1Rnrn4ktfn9lYYXi_FVU4UI,7061
|
|
46
46
|
trilogy/core/processing/node_generators/node_merge_node.py,sha256=sv55oynfqgpHEpo1OEtVDri-5fywzPhDlR85qaWikvY,16195
|
|
47
47
|
trilogy/core/processing/node_generators/rowset_node.py,sha256=8yeMWiyi9IFnza7qPn9YYC3WpA53weq3AY5WisIui8Y,6705
|
|
48
48
|
trilogy/core/processing/node_generators/select_merge_node.py,sha256=VHCPMbnKfg7AOfoYa6PKxpNni-j5JEfliNUiltmZhds,19698
|
|
@@ -63,7 +63,7 @@ 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=
|
|
66
|
+
trilogy/core/statements/author.py,sha256=9wKZDwQ-BeaUCMjD9l0ffMMv8zivaYcAg12UhVFi-0Y,14248
|
|
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
|
|
@@ -71,8 +71,8 @@ trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
71
71
|
trilogy/dialect/base.py,sha256=u00kIIl98as1QzcduiiyyoBzxRGVeBxfeO5hWlRCAJU,40222
|
|
72
72
|
trilogy/dialect/bigquery.py,sha256=mKC3zoEU232h9RtIXJjqiZ72lWH8a6S28p6wAZKrAfg,2952
|
|
73
73
|
trilogy/dialect/common.py,sha256=cbTo_vamdp8pj9spSjGSH-bSZpy4FpNJ12k5vMvyT2Y,3942
|
|
74
|
-
trilogy/dialect/config.py,sha256=
|
|
75
|
-
trilogy/dialect/dataframe.py,sha256=
|
|
74
|
+
trilogy/dialect/config.py,sha256=EGYRQIbrkeMuud5Bkds7jSD5dCJR5hEYZUYcy-lYZl4,3308
|
|
75
|
+
trilogy/dialect/dataframe.py,sha256=RUbNgReEa9g3pL6H7fP9lPTrAij5pkqedpZ99D8_5AE,1522
|
|
76
76
|
trilogy/dialect/duckdb.py,sha256=2tH_OetgLJoKf_f4bdeeB0ozGC8f0h_xQ271I8qD-Oo,3690
|
|
77
77
|
trilogy/dialect/enums.py,sha256=1KDgds_DC31hGxZzNI_TIggxXF7m9rIjn9KLgNf5WQU,4425
|
|
78
78
|
trilogy/dialect/postgres.py,sha256=VH4EB4myjIeZTHeFU6vK00GxY9c53rCBjg2mLbdaCEE,3254
|
|
@@ -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=
|
|
88
|
+
trilogy/parsing/common.py,sha256=RwO9CdNYy3KxJCjg5Ta_xJwfZHV2PuRErxg66Cs50ws,20490
|
|
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=
|
|
92
|
+
trilogy/parsing/parse_engine.py,sha256=uZ6MYjg6kkTm5HFfOKLGvVvzHiGgH-vY7lV-AIlIBgY,55701
|
|
93
93
|
trilogy/parsing/render.py,sha256=o_XuQWhcwx1lD9eGVqkqZEwkmQK0HdmWWokGBtdeH4I,17837
|
|
94
|
-
trilogy/parsing/trilogy.lark,sha256=
|
|
94
|
+
trilogy/parsing/trilogy.lark,sha256=wZpqI1louDqm-t-TpmzW749dPA9w2EIAyowyEJIeXAM,12620
|
|
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.
|
|
98
|
-
pytrilogy-0.0.3.
|
|
99
|
-
pytrilogy-0.0.3.
|
|
100
|
-
pytrilogy-0.0.3.
|
|
101
|
-
pytrilogy-0.0.3.
|
|
102
|
-
pytrilogy-0.0.3.
|
|
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,,
|
trilogy/__init__.py
CHANGED
trilogy/core/models/author.py
CHANGED
|
@@ -75,6 +75,9 @@ class Mergeable(ABC):
|
|
|
75
75
|
def with_merge(self, source: Concept, target: Concept, modifiers: List[Modifier]):
|
|
76
76
|
raise NotImplementedError
|
|
77
77
|
|
|
78
|
+
def with_reference_replacement(self, source: str, target: Expr):
|
|
79
|
+
raise NotImplementedError(type(self))
|
|
80
|
+
|
|
78
81
|
|
|
79
82
|
class ConceptArgs(ABC):
|
|
80
83
|
@property
|
|
@@ -152,6 +155,11 @@ class ConceptRef(Addressable, Namespaced, DataTyped, Mergeable, BaseModel):
|
|
|
152
155
|
metadata=self.metadata,
|
|
153
156
|
)
|
|
154
157
|
|
|
158
|
+
def with_reference_replacement(self, source: str, target: Expr):
|
|
159
|
+
if self.address == source:
|
|
160
|
+
return target
|
|
161
|
+
return self
|
|
162
|
+
|
|
155
163
|
|
|
156
164
|
class UndefinedConcept(ConceptRef):
|
|
157
165
|
pass
|
|
@@ -409,7 +417,7 @@ class Grain(Namespaced, BaseModel):
|
|
|
409
417
|
) -> Grain:
|
|
410
418
|
from trilogy.parsing.common import concepts_to_grain_concepts
|
|
411
419
|
|
|
412
|
-
|
|
420
|
+
x = Grain.model_construct(
|
|
413
421
|
components={
|
|
414
422
|
c.address
|
|
415
423
|
for c in concepts_to_grain_concepts(concepts, environment=environment)
|
|
@@ -417,6 +425,8 @@ class Grain(Namespaced, BaseModel):
|
|
|
417
425
|
where_clause=where_clause,
|
|
418
426
|
)
|
|
419
427
|
|
|
428
|
+
return x
|
|
429
|
+
|
|
420
430
|
def with_namespace(self, namespace: str) -> "Grain":
|
|
421
431
|
return Grain.model_construct(
|
|
422
432
|
components={address_with_namespace(c, namespace) for c in self.components},
|
|
@@ -662,6 +672,21 @@ class Comparison(ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
|
662
672
|
operator=self.operator,
|
|
663
673
|
)
|
|
664
674
|
|
|
675
|
+
def with_reference_replacement(self, source, target):
|
|
676
|
+
return self.__class__.model_construct(
|
|
677
|
+
left=(
|
|
678
|
+
self.left.with_reference_replacement(source, target)
|
|
679
|
+
if isinstance(self.left, Mergeable)
|
|
680
|
+
else self.left
|
|
681
|
+
),
|
|
682
|
+
right=(
|
|
683
|
+
self.right.with_reference_replacement(source, target)
|
|
684
|
+
if isinstance(self.right, Mergeable)
|
|
685
|
+
else self.right
|
|
686
|
+
),
|
|
687
|
+
operator=self.operator,
|
|
688
|
+
)
|
|
689
|
+
|
|
665
690
|
def with_namespace(self, namespace: str):
|
|
666
691
|
return self.__class__.model_construct(
|
|
667
692
|
left=(
|
|
@@ -1233,19 +1258,9 @@ class OrderItem(Mergeable, ConceptArgs, Namespaced, BaseModel):
|
|
|
1233
1258
|
order=self.order,
|
|
1234
1259
|
)
|
|
1235
1260
|
|
|
1236
|
-
@property
|
|
1237
|
-
def output(self):
|
|
1238
|
-
return self.expr.output
|
|
1239
|
-
|
|
1240
1261
|
@property
|
|
1241
1262
|
def concept_arguments(self) -> Sequence[ConceptRef]:
|
|
1242
|
-
|
|
1243
|
-
x = self.expr
|
|
1244
|
-
if isinstance(x, ConceptRef):
|
|
1245
|
-
base += [x]
|
|
1246
|
-
elif isinstance(x, ConceptArgs):
|
|
1247
|
-
base += x.concept_arguments
|
|
1248
|
-
return base
|
|
1263
|
+
return get_concept_arguments(self.expr)
|
|
1249
1264
|
|
|
1250
1265
|
@property
|
|
1251
1266
|
def row_arguments(self) -> Sequence[ConceptRef]:
|
|
@@ -1316,31 +1331,19 @@ class WindowItem(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
|
1316
1331
|
|
|
1317
1332
|
@property
|
|
1318
1333
|
def concept_arguments(self) -> List[ConceptRef]:
|
|
1319
|
-
return self.arguments
|
|
1320
|
-
|
|
1321
|
-
@property
|
|
1322
|
-
def arguments(self) -> List[ConceptRef]:
|
|
1323
1334
|
output = [self.content]
|
|
1324
1335
|
for order in self.order_by:
|
|
1325
|
-
output +=
|
|
1336
|
+
output += get_concept_arguments(order)
|
|
1326
1337
|
for item in self.over:
|
|
1327
|
-
output +=
|
|
1338
|
+
output += get_concept_arguments(item)
|
|
1328
1339
|
return output
|
|
1329
1340
|
|
|
1330
|
-
@property
|
|
1331
|
-
def output(self) -> ConceptRef:
|
|
1332
|
-
return self.content
|
|
1333
|
-
|
|
1334
1341
|
@property
|
|
1335
1342
|
def output_datatype(self):
|
|
1336
1343
|
if self.type in (WindowType.RANK, WindowType.ROW_NUMBER):
|
|
1337
1344
|
return DataType.INTEGER
|
|
1338
1345
|
return self.content.output_datatype
|
|
1339
1346
|
|
|
1340
|
-
@property
|
|
1341
|
-
def output_purpose(self):
|
|
1342
|
-
return Purpose.PROPERTY
|
|
1343
|
-
|
|
1344
1347
|
|
|
1345
1348
|
def get_basic_type(
|
|
1346
1349
|
type: DataType | ListType | StructType | MapType | NumericType,
|
|
@@ -1407,12 +1410,28 @@ class CaseWhen(Namespaced, ConceptArgs, Mergeable, BaseModel):
|
|
|
1407
1410
|
),
|
|
1408
1411
|
)
|
|
1409
1412
|
|
|
1413
|
+
def with_reference_replacement(self, source, target):
|
|
1414
|
+
return CaseWhen.model_construct(
|
|
1415
|
+
comparison=self.comparison.with_reference_replacement(source, target),
|
|
1416
|
+
expr=(
|
|
1417
|
+
self.expr.with_reference_replacement(source, target)
|
|
1418
|
+
if isinstance(self.expr, Mergeable)
|
|
1419
|
+
else self.expr
|
|
1420
|
+
),
|
|
1421
|
+
)
|
|
1422
|
+
|
|
1410
1423
|
|
|
1411
1424
|
class CaseElse(Namespaced, ConceptArgs, Mergeable, BaseModel):
|
|
1412
1425
|
expr: "Expr"
|
|
1413
1426
|
# this ensures that it's easily differentiable from CaseWhen
|
|
1414
1427
|
discriminant: ComparisonOperator = ComparisonOperator.ELSE
|
|
1415
1428
|
|
|
1429
|
+
def __str__(self):
|
|
1430
|
+
return self.__repr__()
|
|
1431
|
+
|
|
1432
|
+
def __repr__(self):
|
|
1433
|
+
return f"ELSE {str(self.expr)}"
|
|
1434
|
+
|
|
1416
1435
|
@field_validator("expr", mode="before")
|
|
1417
1436
|
def enforce_expr(cls, v):
|
|
1418
1437
|
if isinstance(v, Concept):
|
|
@@ -1435,6 +1454,19 @@ class CaseElse(Namespaced, ConceptArgs, Mergeable, BaseModel):
|
|
|
1435
1454
|
),
|
|
1436
1455
|
)
|
|
1437
1456
|
|
|
1457
|
+
def with_reference_replacement(self, source, target):
|
|
1458
|
+
return CaseElse.model_construct(
|
|
1459
|
+
discriminant=self.discriminant,
|
|
1460
|
+
expr=(
|
|
1461
|
+
self.expr.with_reference_replacement(
|
|
1462
|
+
source,
|
|
1463
|
+
target,
|
|
1464
|
+
)
|
|
1465
|
+
if isinstance(self.expr, Mergeable)
|
|
1466
|
+
else self.expr
|
|
1467
|
+
),
|
|
1468
|
+
)
|
|
1469
|
+
|
|
1438
1470
|
def with_namespace(self, namespace: str) -> CaseElse:
|
|
1439
1471
|
return CaseElse.model_construct(
|
|
1440
1472
|
discriminant=self.discriminant,
|
|
@@ -1510,8 +1542,6 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
|
1510
1542
|
|
|
1511
1543
|
def __init__(self, **kwargs):
|
|
1512
1544
|
super().__init__(**kwargs)
|
|
1513
|
-
if "datatype" in str(self):
|
|
1514
|
-
raise SyntaxError(str(self))
|
|
1515
1545
|
|
|
1516
1546
|
def __repr__(self):
|
|
1517
1547
|
return f'{self.operator.value}({",".join([str(a) for a in self.arguments])})'
|
|
@@ -1597,6 +1627,29 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
|
1597
1627
|
)
|
|
1598
1628
|
return v
|
|
1599
1629
|
|
|
1630
|
+
def with_reference_replacement(self, source: str, target: Expr):
|
|
1631
|
+
return Function.model_construct(
|
|
1632
|
+
operator=self.operator,
|
|
1633
|
+
arguments=[
|
|
1634
|
+
(
|
|
1635
|
+
c.with_reference_replacement(
|
|
1636
|
+
source,
|
|
1637
|
+
target,
|
|
1638
|
+
)
|
|
1639
|
+
if isinstance(
|
|
1640
|
+
c,
|
|
1641
|
+
Mergeable,
|
|
1642
|
+
)
|
|
1643
|
+
else c
|
|
1644
|
+
)
|
|
1645
|
+
for c in self.arguments
|
|
1646
|
+
],
|
|
1647
|
+
output_datatype=self.output_datatype,
|
|
1648
|
+
output_purpose=self.output_purpose,
|
|
1649
|
+
valid_inputs=self.valid_inputs,
|
|
1650
|
+
arg_count=self.arg_count,
|
|
1651
|
+
)
|
|
1652
|
+
|
|
1600
1653
|
def with_namespace(self, namespace: str) -> "Function":
|
|
1601
1654
|
return Function.model_construct(
|
|
1602
1655
|
operator=self.operator,
|
|
@@ -1658,10 +1711,13 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
|
1658
1711
|
return base_grain
|
|
1659
1712
|
|
|
1660
1713
|
|
|
1661
|
-
class AggregateWrapper(Mergeable, ConceptArgs, Namespaced, BaseModel):
|
|
1714
|
+
class AggregateWrapper(Mergeable, DataTyped, ConceptArgs, Namespaced, BaseModel):
|
|
1662
1715
|
function: Function
|
|
1663
1716
|
by: List[ConceptRef] = Field(default_factory=list)
|
|
1664
1717
|
|
|
1718
|
+
def __init__(self, **kwargs):
|
|
1719
|
+
super().__init__(**kwargs)
|
|
1720
|
+
|
|
1665
1721
|
@field_validator("by", mode="before")
|
|
1666
1722
|
@classmethod
|
|
1667
1723
|
def enforce_concept_ref(cls, v):
|
|
@@ -1693,10 +1749,6 @@ class AggregateWrapper(Mergeable, ConceptArgs, Namespaced, BaseModel):
|
|
|
1693
1749
|
def output_purpose(self):
|
|
1694
1750
|
return self.function.output_purpose
|
|
1695
1751
|
|
|
1696
|
-
@property
|
|
1697
|
-
def arguments(self):
|
|
1698
|
-
return self.function.arguments
|
|
1699
|
-
|
|
1700
1752
|
def with_merge(self, source: Concept, target: Concept, modifiers: List[Modifier]):
|
|
1701
1753
|
return AggregateWrapper.model_construct(
|
|
1702
1754
|
function=self.function.with_merge(source, target, modifiers=modifiers),
|
|
@@ -1707,6 +1759,16 @@ class AggregateWrapper(Mergeable, ConceptArgs, Namespaced, BaseModel):
|
|
|
1707
1759
|
),
|
|
1708
1760
|
)
|
|
1709
1761
|
|
|
1762
|
+
def with_reference_replacement(self, source, target):
|
|
1763
|
+
return AggregateWrapper.model_construct(
|
|
1764
|
+
function=self.function.with_reference_replacement(source, target),
|
|
1765
|
+
by=(
|
|
1766
|
+
[c.with_reference_replacement(source, target) for c in self.by]
|
|
1767
|
+
if self.by
|
|
1768
|
+
else []
|
|
1769
|
+
),
|
|
1770
|
+
)
|
|
1771
|
+
|
|
1710
1772
|
def with_namespace(self, namespace: str) -> "AggregateWrapper":
|
|
1711
1773
|
return AggregateWrapper.model_construct(
|
|
1712
1774
|
function=self.function.with_namespace(namespace),
|
|
@@ -1796,11 +1858,6 @@ class RowsetItem(Mergeable, ConceptArgs, Namespaced, BaseModel):
|
|
|
1796
1858
|
rowset=self.rowset.with_namespace(namespace),
|
|
1797
1859
|
)
|
|
1798
1860
|
|
|
1799
|
-
@property
|
|
1800
|
-
def arguments(self) -> List[ConceptRef]:
|
|
1801
|
-
output = [self.content]
|
|
1802
|
-
return output
|
|
1803
|
-
|
|
1804
1861
|
@property
|
|
1805
1862
|
def output(self) -> ConceptRef:
|
|
1806
1863
|
return self.content
|
|
@@ -1831,7 +1888,10 @@ class OrderBy(Mergeable, Namespaced, BaseModel):
|
|
|
1831
1888
|
|
|
1832
1889
|
@property
|
|
1833
1890
|
def concept_arguments(self):
|
|
1834
|
-
|
|
1891
|
+
base = []
|
|
1892
|
+
for x in self.items:
|
|
1893
|
+
base += x.concept_arguments
|
|
1894
|
+
return base
|
|
1835
1895
|
|
|
1836
1896
|
|
|
1837
1897
|
class AlignClause(Namespaced, BaseModel):
|
|
@@ -2088,6 +2148,11 @@ class Comment(BaseModel):
|
|
|
2088
2148
|
text: str
|
|
2089
2149
|
|
|
2090
2150
|
|
|
2151
|
+
class ArgBinding(BaseModel):
|
|
2152
|
+
name: str
|
|
2153
|
+
default: Expr | None = None
|
|
2154
|
+
|
|
2155
|
+
|
|
2091
2156
|
Expr = (
|
|
2092
2157
|
MagicConstants
|
|
2093
2158
|
| bool
|
trilogy/core/models/build.py
CHANGED
|
@@ -1639,9 +1639,26 @@ class Factory:
|
|
|
1639
1639
|
|
|
1640
1640
|
@build.register
|
|
1641
1641
|
def _(self, base: Comparison) -> BuildComparison:
|
|
1642
|
+
from trilogy.parsing.common import arbitrary_to_concept
|
|
1643
|
+
|
|
1644
|
+
left = base.left
|
|
1645
|
+
if isinstance(left, AggregateWrapper):
|
|
1646
|
+
left_c = arbitrary_to_concept(
|
|
1647
|
+
left,
|
|
1648
|
+
environment=self.environment,
|
|
1649
|
+
)
|
|
1650
|
+
left = left_c # type: ignore
|
|
1651
|
+
right = base.right
|
|
1652
|
+
if isinstance(right, AggregateWrapper):
|
|
1653
|
+
right_c = arbitrary_to_concept(
|
|
1654
|
+
right,
|
|
1655
|
+
environment=self.environment,
|
|
1656
|
+
)
|
|
1657
|
+
|
|
1658
|
+
right = right_c # type: ignore
|
|
1642
1659
|
return BuildComparison.model_construct(
|
|
1643
|
-
left=(self.build(
|
|
1644
|
-
right=(self.build(
|
|
1660
|
+
left=(self.build(left)),
|
|
1661
|
+
right=(self.build(right)),
|
|
1645
1662
|
operator=base.operator,
|
|
1646
1663
|
)
|
|
1647
1664
|
|
|
@@ -1660,7 +1677,7 @@ class Factory:
|
|
|
1660
1677
|
)
|
|
1661
1678
|
|
|
1662
1679
|
@build.register
|
|
1663
|
-
def _(self, base: RowsetItem):
|
|
1680
|
+
def _(self, base: RowsetItem) -> BuildRowsetItem:
|
|
1664
1681
|
|
|
1665
1682
|
factory = Factory(
|
|
1666
1683
|
environment=self.environment,
|
|
@@ -9,6 +9,7 @@ from typing import (
|
|
|
9
9
|
TYPE_CHECKING,
|
|
10
10
|
Annotated,
|
|
11
11
|
Any,
|
|
12
|
+
Callable,
|
|
12
13
|
Dict,
|
|
13
14
|
ItemsView,
|
|
14
15
|
List,
|
|
@@ -189,7 +190,7 @@ class Environment(BaseModel):
|
|
|
189
190
|
datasources: Annotated[
|
|
190
191
|
EnvironmentDatasourceDict, PlainValidator(validate_datasources)
|
|
191
192
|
] = Field(default_factory=EnvironmentDatasourceDict)
|
|
192
|
-
functions: Dict[str,
|
|
193
|
+
functions: Dict[str, Callable[..., Any]] = Field(default_factory=dict)
|
|
193
194
|
data_types: Dict[str, DataType] = Field(default_factory=dict)
|
|
194
195
|
named_statements: Dict[str, SelectLineage] = Field(default_factory=dict)
|
|
195
196
|
imports: Dict[str, list[Import]] = Field(
|
|
@@ -37,7 +37,7 @@ def gen_group_node(
|
|
|
37
37
|
resolve_function_parent_concepts(concept, environment=environment), "address"
|
|
38
38
|
)
|
|
39
39
|
logger.info(
|
|
40
|
-
f"{padding(depth)}{LOGGER_PREFIX} parent concepts for {concept} are {[x.address for x in parent_concepts]} from group grain {concept.grain}"
|
|
40
|
+
f"{padding(depth)}{LOGGER_PREFIX} parent concepts for {concept} {concept.lineage} are {[x.address for x in parent_concepts]} from group grain {concept.grain}"
|
|
41
41
|
)
|
|
42
42
|
|
|
43
43
|
# if the aggregation has a grain, we need to ensure these are the ONLY optional in the output of the select
|
|
@@ -76,7 +76,7 @@ def gen_multiselect_node(
|
|
|
76
76
|
for select in lineage.selects:
|
|
77
77
|
|
|
78
78
|
snode: StrategyNode = get_query_node(history.base_environment, select)
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
logger.info(
|
|
81
81
|
f"{padding(depth)}{LOGGER_PREFIX} Fetched parent node with outputs {select.output_components}"
|
|
82
82
|
)
|
|
@@ -127,7 +127,9 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
|
|
|
127
127
|
order_by=order_by,
|
|
128
128
|
meta=meta or Metadata(),
|
|
129
129
|
)
|
|
130
|
+
|
|
130
131
|
output.grain = output.calculate_grain(environment)
|
|
132
|
+
|
|
131
133
|
for x in selection:
|
|
132
134
|
|
|
133
135
|
if x.is_undefined and environment.concepts.fail_on_missing:
|
trilogy/dialect/config.py
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
from
|
|
1
|
+
from typing import TYPE_CHECKING, Any
|
|
2
|
+
|
|
3
|
+
if TYPE_CHECKING:
|
|
4
|
+
try:
|
|
5
|
+
from pandas import DataFrame
|
|
6
|
+
except ImportError:
|
|
7
|
+
DataFrame = Any
|
|
2
8
|
|
|
3
9
|
|
|
4
10
|
class DialectConfig:
|
|
@@ -110,6 +116,6 @@ class TrinoConfig(PrestoConfig):
|
|
|
110
116
|
|
|
111
117
|
|
|
112
118
|
class DataFrameConfig(DuckDBConfig):
|
|
113
|
-
def __init__(self, dataframes: dict[str, DataFrame]):
|
|
119
|
+
def __init__(self, dataframes: dict[str, "DataFrame"]):
|
|
114
120
|
super().__init__()
|
|
115
121
|
self.dataframes = dataframes
|
trilogy/dialect/dataframe.py
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
|
-
from typing import Any
|
|
1
|
+
from typing import TYPE_CHECKING, Any
|
|
2
2
|
|
|
3
|
-
from pandas import DataFrame
|
|
4
3
|
from sqlalchemy import text
|
|
5
4
|
|
|
6
5
|
from trilogy.core.models.environment import Environment
|
|
7
6
|
from trilogy.dialect.duckdb import DuckDBDialect
|
|
8
7
|
from trilogy.engine import ExecutionEngine
|
|
9
8
|
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
try:
|
|
11
|
+
from pandas import DataFrame
|
|
12
|
+
except ImportError:
|
|
13
|
+
DataFrame = Any
|
|
14
|
+
|
|
10
15
|
|
|
11
16
|
class DataframeDialect(DuckDBDialect):
|
|
12
17
|
pass
|
|
13
18
|
|
|
14
19
|
|
|
15
20
|
class DataframeConnectionWrapper(ExecutionEngine):
|
|
16
|
-
def __init__(self, engine: ExecutionEngine, dataframes: dict[str, DataFrame]):
|
|
21
|
+
def __init__(self, engine: ExecutionEngine, dataframes: dict[str, "DataFrame"]):
|
|
17
22
|
self.engine = engine
|
|
18
23
|
self.dataframes = dataframes
|
|
19
24
|
self.connection = None
|
|
@@ -34,7 +39,7 @@ class DataframeConnectionWrapper(ExecutionEngine):
|
|
|
34
39
|
)
|
|
35
40
|
pass
|
|
36
41
|
|
|
37
|
-
def add_dataframe(self, name: str, df: DataFrame, connection, env: Environment):
|
|
42
|
+
def add_dataframe(self, name: str, df: "DataFrame", connection, env: Environment):
|
|
38
43
|
self.dataframes[name] = df
|
|
39
44
|
self._register_dataframes(env, connection)
|
|
40
45
|
|
trilogy/parsing/common.py
CHANGED
|
@@ -46,6 +46,62 @@ from trilogy.core.statements.author import RowsetDerivationStatement, SelectStat
|
|
|
46
46
|
from trilogy.utility import string_to_hash, unique
|
|
47
47
|
|
|
48
48
|
|
|
49
|
+
def process_function_arg(
|
|
50
|
+
arg,
|
|
51
|
+
meta: Meta | None,
|
|
52
|
+
environment: Environment,
|
|
53
|
+
):
|
|
54
|
+
# if a function has an anonymous function argument
|
|
55
|
+
# create an implicit concept
|
|
56
|
+
if isinstance(arg, Parenthetical):
|
|
57
|
+
processed = process_function_args([arg.content], meta, environment)
|
|
58
|
+
return Function(
|
|
59
|
+
operator=FunctionType.PARENTHETICAL,
|
|
60
|
+
arguments=processed,
|
|
61
|
+
output_datatype=arg_to_datatype(processed[0]),
|
|
62
|
+
output_purpose=function_args_to_output_purpose(processed),
|
|
63
|
+
)
|
|
64
|
+
elif isinstance(arg, Function):
|
|
65
|
+
# if it's not an aggregate function, we can skip the virtual concepts
|
|
66
|
+
# to simplify anonymous function handling
|
|
67
|
+
if (
|
|
68
|
+
arg.operator not in FunctionClass.AGGREGATE_FUNCTIONS.value
|
|
69
|
+
and arg.operator != FunctionType.UNNEST
|
|
70
|
+
):
|
|
71
|
+
return arg
|
|
72
|
+
id_hash = string_to_hash(str(arg))
|
|
73
|
+
name = f"{VIRTUAL_CONCEPT_PREFIX}_{arg.operator.value}_{id_hash}"
|
|
74
|
+
if f"{environment.namespace}.{name}" in environment.concepts:
|
|
75
|
+
return environment.concepts[f"{environment.namespace}.{name}"]
|
|
76
|
+
concept = function_to_concept(
|
|
77
|
+
arg,
|
|
78
|
+
name=name,
|
|
79
|
+
environment=environment,
|
|
80
|
+
)
|
|
81
|
+
# to satisfy mypy, concept will always have metadata
|
|
82
|
+
if concept.metadata and meta:
|
|
83
|
+
concept.metadata.line_number = meta.line
|
|
84
|
+
environment.add_concept(concept, meta=meta)
|
|
85
|
+
return concept
|
|
86
|
+
elif isinstance(
|
|
87
|
+
arg, (FilterItem, WindowItem, AggregateWrapper, ListWrapper, MapWrapper)
|
|
88
|
+
):
|
|
89
|
+
id_hash = string_to_hash(str(arg))
|
|
90
|
+
name = f"{VIRTUAL_CONCEPT_PREFIX}_{id_hash}"
|
|
91
|
+
if f"{environment.namespace}.{name}" in environment.concepts:
|
|
92
|
+
return environment.concepts[f"{environment.namespace}.{name}"]
|
|
93
|
+
concept = arbitrary_to_concept(
|
|
94
|
+
arg,
|
|
95
|
+
name=name,
|
|
96
|
+
environment=environment,
|
|
97
|
+
)
|
|
98
|
+
if concept.metadata and meta:
|
|
99
|
+
concept.metadata.line_number = meta.line
|
|
100
|
+
environment.add_concept(concept, meta=meta)
|
|
101
|
+
return concept
|
|
102
|
+
return arg
|
|
103
|
+
|
|
104
|
+
|
|
49
105
|
def process_function_args(
|
|
50
106
|
args,
|
|
51
107
|
meta: Meta | None,
|
|
@@ -53,54 +109,7 @@ def process_function_args(
|
|
|
53
109
|
) -> List[Concept | Function | str | int | float | date | datetime]:
|
|
54
110
|
final: List[Concept | Function | str | int | float | date | datetime] = []
|
|
55
111
|
for arg in args:
|
|
56
|
-
|
|
57
|
-
# create an implicit concept
|
|
58
|
-
if isinstance(arg, Parenthetical):
|
|
59
|
-
processed = process_function_args([arg.content], meta, environment)
|
|
60
|
-
final.append(
|
|
61
|
-
Function(
|
|
62
|
-
operator=FunctionType.PARENTHETICAL,
|
|
63
|
-
arguments=processed,
|
|
64
|
-
output_datatype=arg_to_datatype(processed[0]),
|
|
65
|
-
output_purpose=function_args_to_output_purpose(processed),
|
|
66
|
-
)
|
|
67
|
-
)
|
|
68
|
-
elif isinstance(arg, Function):
|
|
69
|
-
# if it's not an aggregate function, we can skip the virtual concepts
|
|
70
|
-
# to simplify anonymous function handling
|
|
71
|
-
if (
|
|
72
|
-
arg.operator not in FunctionClass.AGGREGATE_FUNCTIONS.value
|
|
73
|
-
and arg.operator != FunctionType.UNNEST
|
|
74
|
-
):
|
|
75
|
-
final.append(arg)
|
|
76
|
-
continue
|
|
77
|
-
id_hash = string_to_hash(str(arg))
|
|
78
|
-
concept = function_to_concept(
|
|
79
|
-
arg,
|
|
80
|
-
name=f"{VIRTUAL_CONCEPT_PREFIX}_{arg.operator.value}_{id_hash}",
|
|
81
|
-
environment=environment,
|
|
82
|
-
)
|
|
83
|
-
# to satisfy mypy, concept will always have metadata
|
|
84
|
-
if concept.metadata and meta:
|
|
85
|
-
concept.metadata.line_number = meta.line
|
|
86
|
-
environment.add_concept(concept, meta=meta)
|
|
87
|
-
final.append(concept)
|
|
88
|
-
elif isinstance(
|
|
89
|
-
arg, (FilterItem, WindowItem, AggregateWrapper, ListWrapper, MapWrapper)
|
|
90
|
-
):
|
|
91
|
-
id_hash = string_to_hash(str(arg))
|
|
92
|
-
concept = arbitrary_to_concept(
|
|
93
|
-
arg,
|
|
94
|
-
name=f"{VIRTUAL_CONCEPT_PREFIX}_{id_hash}",
|
|
95
|
-
environment=environment,
|
|
96
|
-
)
|
|
97
|
-
if concept.metadata and meta:
|
|
98
|
-
concept.metadata.line_number = meta.line
|
|
99
|
-
environment.add_concept(concept, meta=meta)
|
|
100
|
-
final.append(concept)
|
|
101
|
-
|
|
102
|
-
else:
|
|
103
|
-
final.append(arg)
|
|
112
|
+
final.append(process_function_arg(arg, meta, environment))
|
|
104
113
|
return final
|
|
105
114
|
|
|
106
115
|
|
trilogy/parsing/parse_engine.py
CHANGED
|
@@ -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 List, Optional, Tuple, Union
|
|
7
|
+
from typing import Callable, List, Optional, Tuple, Union
|
|
8
8
|
|
|
9
9
|
from lark import Lark, ParseTree, Transformer, Tree, v_args
|
|
10
10
|
from lark.exceptions import (
|
|
@@ -49,6 +49,7 @@ from trilogy.core.models.author import (
|
|
|
49
49
|
AggregateWrapper,
|
|
50
50
|
AlignClause,
|
|
51
51
|
AlignItem,
|
|
52
|
+
ArgBinding,
|
|
52
53
|
CaseElse,
|
|
53
54
|
CaseWhen,
|
|
54
55
|
Comment,
|
|
@@ -61,6 +62,7 @@ from trilogy.core.models.author import (
|
|
|
61
62
|
Function,
|
|
62
63
|
Grain,
|
|
63
64
|
HavingClause,
|
|
65
|
+
Mergeable,
|
|
64
66
|
Metadata,
|
|
65
67
|
OrderBy,
|
|
66
68
|
OrderItem,
|
|
@@ -1075,27 +1077,45 @@ class ParseToObjects(Transformer):
|
|
|
1075
1077
|
return HavingClause(conditional=root)
|
|
1076
1078
|
|
|
1077
1079
|
@v_args(meta=True)
|
|
1078
|
-
def function_binding_list(self, meta: Meta, args) ->
|
|
1080
|
+
def function_binding_list(self, meta: Meta, args) -> list[ArgBinding]:
|
|
1079
1081
|
return args
|
|
1080
1082
|
|
|
1081
1083
|
@v_args(meta=True)
|
|
1082
|
-
def function_binding_item(self, meta: Meta, args) ->
|
|
1083
|
-
|
|
1084
|
+
def function_binding_item(self, meta: Meta, args) -> ArgBinding:
|
|
1085
|
+
if len(args) == 2:
|
|
1086
|
+
return ArgBinding(name=args[0], default=args[1])
|
|
1087
|
+
return ArgBinding(name=args[0], default=None)
|
|
1084
1088
|
|
|
1085
1089
|
@v_args(meta=True)
|
|
1086
|
-
def raw_function(self, meta: Meta, args) ->
|
|
1090
|
+
def raw_function(self, meta: Meta, args) -> Callable[[list[Expr]], Expr]:
|
|
1087
1091
|
identity = args[0]
|
|
1088
|
-
|
|
1092
|
+
function_arguments: list[ArgBinding] = args[1]
|
|
1089
1093
|
output = args[2]
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1094
|
+
|
|
1095
|
+
def function_factory(*creation_args: list[Expr]):
|
|
1096
|
+
nout = output.copy(deep=True)
|
|
1097
|
+
creation_arg_list: list[Expr] = list(creation_args)
|
|
1098
|
+
if len(creation_args) < len(function_arguments):
|
|
1099
|
+
for binding in function_arguments[len(creation_arg_list) :]:
|
|
1100
|
+
if binding.default is None:
|
|
1101
|
+
raise ValueError(f"Missing argument {binding.name}")
|
|
1102
|
+
creation_arg_list.append(binding.default)
|
|
1103
|
+
if isinstance(nout, Mergeable):
|
|
1104
|
+
for idx, x in enumerate(creation_arg_list):
|
|
1105
|
+
# these will always be local namespace
|
|
1106
|
+
nout = nout.with_reference_replacement(
|
|
1107
|
+
f"{DEFAULT_NAMESPACE}.{function_arguments[idx].name}", x
|
|
1108
|
+
)
|
|
1109
|
+
return nout
|
|
1110
|
+
|
|
1111
|
+
self.environment.functions[identity] = function_factory
|
|
1112
|
+
return function_factory
|
|
1113
|
+
|
|
1114
|
+
def custom_function(self, args):
|
|
1115
|
+
name = args[0]
|
|
1116
|
+
args = args[1:]
|
|
1117
|
+
remapped = self.environment.functions[name](*args)
|
|
1118
|
+
return remapped
|
|
1099
1119
|
|
|
1100
1120
|
@v_args(meta=True)
|
|
1101
1121
|
def function(self, meta: Meta, args) -> Function:
|
|
@@ -1141,24 +1161,24 @@ class ParseToObjects(Transformer):
|
|
|
1141
1161
|
def comparison(self, args) -> Comparison:
|
|
1142
1162
|
if args[1] == ComparisonOperator.IN:
|
|
1143
1163
|
raise SyntaxError
|
|
1144
|
-
if isinstance(args[0], AggregateWrapper):
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
else:
|
|
1152
|
-
|
|
1153
|
-
if isinstance(args[2], AggregateWrapper):
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
else:
|
|
1161
|
-
|
|
1164
|
+
# if isinstance(args[0], AggregateWrapper):
|
|
1165
|
+
# left_c = arbitrary_to_concept(
|
|
1166
|
+
# args[0],
|
|
1167
|
+
# environment=self.environment,
|
|
1168
|
+
# )
|
|
1169
|
+
# self.environment.add_concept(left_c)
|
|
1170
|
+
# left = left_c.reference
|
|
1171
|
+
# else:
|
|
1172
|
+
left = args[0]
|
|
1173
|
+
# if isinstance(args[2], AggregateWrapper):
|
|
1174
|
+
# right_c = arbitrary_to_concept(
|
|
1175
|
+
# args[2],
|
|
1176
|
+
# environment=self.environment,
|
|
1177
|
+
# )
|
|
1178
|
+
# self.environment.add_concept(right_c)
|
|
1179
|
+
# right = right_c.reference
|
|
1180
|
+
# else:
|
|
1181
|
+
right = args[2]
|
|
1162
1182
|
return Comparison(left=left, right=right, operator=args[1])
|
|
1163
1183
|
|
|
1164
1184
|
def between_comparison(self, args) -> Conditional:
|
trilogy/parsing/trilogy.lark
CHANGED
|
@@ -89,9 +89,9 @@
|
|
|
89
89
|
|
|
90
90
|
// FUNCTION blocks
|
|
91
91
|
function: raw_function
|
|
92
|
-
function_binding_item: IDENTIFIER "
|
|
93
|
-
function_binding_list: function_binding_item
|
|
94
|
-
raw_function: "
|
|
92
|
+
function_binding_item: IDENTIFIER ("=" literal)?
|
|
93
|
+
function_binding_list: (function_binding_item ",")* function_binding_item ","?
|
|
94
|
+
raw_function: "def" IDENTIFIER "(" function_binding_list ")" "->" expr
|
|
95
95
|
|
|
96
96
|
// user_id where state = Mexico
|
|
97
97
|
_filter_alt: IDENTIFIER "?" conditional
|
|
@@ -178,7 +178,7 @@
|
|
|
178
178
|
map_key_access: expr "[" string_lit "]"
|
|
179
179
|
attr_access: expr "." string_lit
|
|
180
180
|
|
|
181
|
-
expr: _constant_functions | window_item | filter_item | subselect_comparison | between_comparison | fgroup | aggregate_functions | unnest | union | _static_functions | literal | concept_lit | index_access | map_key_access | attr_access | parenthetical | expr_tuple | comparison | alt_like
|
|
181
|
+
expr: _constant_functions | window_item | filter_item | subselect_comparison | between_comparison | fgroup | aggregate_functions | unnest | union | _static_functions | literal | concept_lit | index_access | map_key_access | attr_access | parenthetical | expr_tuple | comparison | alt_like | custom_function
|
|
182
182
|
|
|
183
183
|
// functions
|
|
184
184
|
|
|
@@ -297,6 +297,8 @@
|
|
|
297
297
|
|
|
298
298
|
_static_functions: _string_functions | _math_functions | _generic_functions | _date_functions
|
|
299
299
|
|
|
300
|
+
custom_function: "@" IDENTIFIER "(" (expr ",")* expr ")"
|
|
301
|
+
|
|
300
302
|
// base language constructs
|
|
301
303
|
concept_lit: IDENTIFIER
|
|
302
304
|
IDENTIFIER: /[a-zA-Z\_][a-zA-Z0-9\_\.]*/
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|