pytrilogy 0.0.3.7__py3-none-any.whl → 0.0.3.9__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.9.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.3.7.dist-info → pytrilogy-0.0.3.9.dist-info}/RECORD +19 -19
- trilogy/__init__.py +1 -1
- trilogy/core/models/author.py +155 -63
- trilogy/core/models/build.py +35 -5
- 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/executor.py +5 -2
- trilogy/parsing/common.py +88 -56
- trilogy/parsing/parse_engine.py +53 -33
- trilogy/parsing/trilogy.lark +6 -4
- {pytrilogy-0.0.3.7.dist-info → pytrilogy-0.0.3.9.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.3.7.dist-info → pytrilogy-0.0.3.9.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.3.7.dist-info → pytrilogy-0.0.3.9.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.3.7.dist-info → pytrilogy-0.0.3.9.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
trilogy/__init__.py,sha256=
|
|
1
|
+
trilogy/__init__.py,sha256=oZcEJCenpaskC-ViU-OpC-03zsr2chE8ggGCpg2Duws,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
|
|
5
|
-
trilogy/executor.py,sha256=
|
|
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
|
|
@@ -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=eJOz-p20Am5IQPDeBntgIkncB1nUZGhuwcy7WBSbCmM,70240
|
|
24
|
+
trilogy/core/models/build.py,sha256=bO1qYvuGl6LeNGgsfS6ZHAzZBR2lBPLg-QJymp9hgkU,57235
|
|
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=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=
|
|
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.9.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
98
|
+
pytrilogy-0.0.3.9.dist-info/METADATA,sha256=8Bv3VIgAPpBlaRGc1UlrWrMPLb0HYNpy-coiUXMzzK0,8983
|
|
99
|
+
pytrilogy-0.0.3.9.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
100
|
+
pytrilogy-0.0.3.9.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
|
|
101
|
+
pytrilogy-0.0.3.9.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
102
|
+
pytrilogy-0.0.3.9.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},
|
|
@@ -537,6 +547,7 @@ class Comparison(ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
|
537
547
|
MagicConstants,
|
|
538
548
|
WindowItem,
|
|
539
549
|
AggregateWrapper,
|
|
550
|
+
FilterItem,
|
|
540
551
|
]
|
|
541
552
|
right: Union[
|
|
542
553
|
int,
|
|
@@ -556,6 +567,7 @@ class Comparison(ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
|
556
567
|
WindowItem,
|
|
557
568
|
AggregateWrapper,
|
|
558
569
|
TupleWrapper,
|
|
570
|
+
FilterItem,
|
|
559
571
|
]
|
|
560
572
|
operator: ComparisonOperator
|
|
561
573
|
|
|
@@ -662,6 +674,21 @@ class Comparison(ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
|
662
674
|
operator=self.operator,
|
|
663
675
|
)
|
|
664
676
|
|
|
677
|
+
def with_reference_replacement(self, source, target):
|
|
678
|
+
return self.__class__.model_construct(
|
|
679
|
+
left=(
|
|
680
|
+
self.left.with_reference_replacement(source, target)
|
|
681
|
+
if isinstance(self.left, Mergeable)
|
|
682
|
+
else self.left
|
|
683
|
+
),
|
|
684
|
+
right=(
|
|
685
|
+
self.right.with_reference_replacement(source, target)
|
|
686
|
+
if isinstance(self.right, Mergeable)
|
|
687
|
+
else self.right
|
|
688
|
+
),
|
|
689
|
+
operator=self.operator,
|
|
690
|
+
)
|
|
691
|
+
|
|
665
692
|
def with_namespace(self, namespace: str):
|
|
666
693
|
return self.__class__.model_construct(
|
|
667
694
|
left=(
|
|
@@ -1224,7 +1251,7 @@ class OrderItem(Mergeable, ConceptArgs, Namespaced, BaseModel):
|
|
|
1224
1251
|
def with_merge(
|
|
1225
1252
|
self, source: Concept, target: Concept, modifiers: List[Modifier]
|
|
1226
1253
|
) -> "OrderItem":
|
|
1227
|
-
return OrderItem(
|
|
1254
|
+
return OrderItem.model_construct(
|
|
1228
1255
|
expr=(
|
|
1229
1256
|
self.expr.with_merge(source, target, modifiers)
|
|
1230
1257
|
if isinstance(self.expr, Mergeable)
|
|
@@ -1233,19 +1260,19 @@ class OrderItem(Mergeable, ConceptArgs, Namespaced, BaseModel):
|
|
|
1233
1260
|
order=self.order,
|
|
1234
1261
|
)
|
|
1235
1262
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1263
|
+
def with_reference_replacement(self, source, target):
|
|
1264
|
+
return OrderItem.model_construct(
|
|
1265
|
+
expr=(
|
|
1266
|
+
self.expr.with_reference_replacement(source, target)
|
|
1267
|
+
if isinstance(self.expr, Mergeable)
|
|
1268
|
+
else self.expr
|
|
1269
|
+
),
|
|
1270
|
+
order=self.order,
|
|
1271
|
+
)
|
|
1239
1272
|
|
|
1240
1273
|
@property
|
|
1241
1274
|
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
|
|
1275
|
+
return get_concept_arguments(self.expr)
|
|
1249
1276
|
|
|
1250
1277
|
@property
|
|
1251
1278
|
def row_arguments(self) -> Sequence[ConceptRef]:
|
|
@@ -1305,6 +1332,17 @@ class WindowItem(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
|
1305
1332
|
)
|
|
1306
1333
|
return output
|
|
1307
1334
|
|
|
1335
|
+
def with_reference_replacement(self, source, target):
|
|
1336
|
+
return WindowItem.model_construct(
|
|
1337
|
+
type=self.type,
|
|
1338
|
+
content=self.content.with_reference_replacement(source, target),
|
|
1339
|
+
over=[x.with_reference_replacement(source, target) for x in self.over],
|
|
1340
|
+
order_by=[
|
|
1341
|
+
x.with_reference_replacement(source, target) for x in self.order_by
|
|
1342
|
+
],
|
|
1343
|
+
index=self.index,
|
|
1344
|
+
)
|
|
1345
|
+
|
|
1308
1346
|
def with_namespace(self, namespace: str) -> "WindowItem":
|
|
1309
1347
|
return WindowItem.model_construct(
|
|
1310
1348
|
type=self.type,
|
|
@@ -1316,31 +1354,19 @@ class WindowItem(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
|
1316
1354
|
|
|
1317
1355
|
@property
|
|
1318
1356
|
def concept_arguments(self) -> List[ConceptRef]:
|
|
1319
|
-
return self.arguments
|
|
1320
|
-
|
|
1321
|
-
@property
|
|
1322
|
-
def arguments(self) -> List[ConceptRef]:
|
|
1323
1357
|
output = [self.content]
|
|
1324
1358
|
for order in self.order_by:
|
|
1325
|
-
output +=
|
|
1359
|
+
output += get_concept_arguments(order)
|
|
1326
1360
|
for item in self.over:
|
|
1327
|
-
output +=
|
|
1361
|
+
output += get_concept_arguments(item)
|
|
1328
1362
|
return output
|
|
1329
1363
|
|
|
1330
|
-
@property
|
|
1331
|
-
def output(self) -> ConceptRef:
|
|
1332
|
-
return self.content
|
|
1333
|
-
|
|
1334
1364
|
@property
|
|
1335
1365
|
def output_datatype(self):
|
|
1336
1366
|
if self.type in (WindowType.RANK, WindowType.ROW_NUMBER):
|
|
1337
1367
|
return DataType.INTEGER
|
|
1338
1368
|
return self.content.output_datatype
|
|
1339
1369
|
|
|
1340
|
-
@property
|
|
1341
|
-
def output_purpose(self):
|
|
1342
|
-
return Purpose.PROPERTY
|
|
1343
|
-
|
|
1344
1370
|
|
|
1345
1371
|
def get_basic_type(
|
|
1346
1372
|
type: DataType | ListType | StructType | MapType | NumericType,
|
|
@@ -1407,12 +1433,28 @@ class CaseWhen(Namespaced, ConceptArgs, Mergeable, BaseModel):
|
|
|
1407
1433
|
),
|
|
1408
1434
|
)
|
|
1409
1435
|
|
|
1436
|
+
def with_reference_replacement(self, source, target):
|
|
1437
|
+
return CaseWhen.model_construct(
|
|
1438
|
+
comparison=self.comparison.with_reference_replacement(source, target),
|
|
1439
|
+
expr=(
|
|
1440
|
+
self.expr.with_reference_replacement(source, target)
|
|
1441
|
+
if isinstance(self.expr, Mergeable)
|
|
1442
|
+
else self.expr
|
|
1443
|
+
),
|
|
1444
|
+
)
|
|
1445
|
+
|
|
1410
1446
|
|
|
1411
1447
|
class CaseElse(Namespaced, ConceptArgs, Mergeable, BaseModel):
|
|
1412
1448
|
expr: "Expr"
|
|
1413
1449
|
# this ensures that it's easily differentiable from CaseWhen
|
|
1414
1450
|
discriminant: ComparisonOperator = ComparisonOperator.ELSE
|
|
1415
1451
|
|
|
1452
|
+
def __str__(self):
|
|
1453
|
+
return self.__repr__()
|
|
1454
|
+
|
|
1455
|
+
def __repr__(self):
|
|
1456
|
+
return f"ELSE {str(self.expr)}"
|
|
1457
|
+
|
|
1416
1458
|
@field_validator("expr", mode="before")
|
|
1417
1459
|
def enforce_expr(cls, v):
|
|
1418
1460
|
if isinstance(v, Concept):
|
|
@@ -1435,6 +1477,19 @@ class CaseElse(Namespaced, ConceptArgs, Mergeable, BaseModel):
|
|
|
1435
1477
|
),
|
|
1436
1478
|
)
|
|
1437
1479
|
|
|
1480
|
+
def with_reference_replacement(self, source, target):
|
|
1481
|
+
return CaseElse.model_construct(
|
|
1482
|
+
discriminant=self.discriminant,
|
|
1483
|
+
expr=(
|
|
1484
|
+
self.expr.with_reference_replacement(
|
|
1485
|
+
source,
|
|
1486
|
+
target,
|
|
1487
|
+
)
|
|
1488
|
+
if isinstance(self.expr, Mergeable)
|
|
1489
|
+
else self.expr
|
|
1490
|
+
),
|
|
1491
|
+
)
|
|
1492
|
+
|
|
1438
1493
|
def with_namespace(self, namespace: str) -> CaseElse:
|
|
1439
1494
|
return CaseElse.model_construct(
|
|
1440
1495
|
discriminant=self.discriminant,
|
|
@@ -1483,35 +1538,10 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
|
1483
1538
|
List[Set[DataType]],
|
|
1484
1539
|
]
|
|
1485
1540
|
] = None
|
|
1486
|
-
arguments: Sequence[
|
|
1487
|
-
Union[
|
|
1488
|
-
ConceptRef,
|
|
1489
|
-
AggregateWrapper,
|
|
1490
|
-
Function,
|
|
1491
|
-
Parenthetical,
|
|
1492
|
-
CaseWhen,
|
|
1493
|
-
CaseElse,
|
|
1494
|
-
WindowItem,
|
|
1495
|
-
int,
|
|
1496
|
-
float,
|
|
1497
|
-
str,
|
|
1498
|
-
date,
|
|
1499
|
-
datetime,
|
|
1500
|
-
MapWrapper[Any, Any],
|
|
1501
|
-
DataType,
|
|
1502
|
-
ListType,
|
|
1503
|
-
MapType,
|
|
1504
|
-
NumericType,
|
|
1505
|
-
DatePart,
|
|
1506
|
-
list,
|
|
1507
|
-
ListWrapper[Any],
|
|
1508
|
-
]
|
|
1509
|
-
]
|
|
1541
|
+
arguments: Sequence[FuncArgs]
|
|
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
|
|
@@ -2099,6 +2164,7 @@ Expr = (
|
|
|
2099
2164
|
| datetime
|
|
2100
2165
|
| TupleWrapper
|
|
2101
2166
|
| ListWrapper
|
|
2167
|
+
| MapWrapper
|
|
2102
2168
|
| WindowItem
|
|
2103
2169
|
| FilterItem
|
|
2104
2170
|
| ConceptRef
|
|
@@ -2107,4 +2173,30 @@ Expr = (
|
|
|
2107
2173
|
| Parenthetical
|
|
2108
2174
|
| Function
|
|
2109
2175
|
| AggregateWrapper
|
|
2176
|
+
| CaseWhen
|
|
2177
|
+
| CaseElse
|
|
2178
|
+
)
|
|
2179
|
+
|
|
2180
|
+
FuncArgs = (
|
|
2181
|
+
ConceptRef
|
|
2182
|
+
| AggregateWrapper
|
|
2183
|
+
| Function
|
|
2184
|
+
| Parenthetical
|
|
2185
|
+
| CaseWhen
|
|
2186
|
+
| CaseElse
|
|
2187
|
+
| WindowItem
|
|
2188
|
+
| FilterItem
|
|
2189
|
+
| int
|
|
2190
|
+
| float
|
|
2191
|
+
| str
|
|
2192
|
+
| date
|
|
2193
|
+
| datetime
|
|
2194
|
+
| MapWrapper[Any, Any]
|
|
2195
|
+
| DataType
|
|
2196
|
+
| ListType
|
|
2197
|
+
| MapType
|
|
2198
|
+
| NumericType
|
|
2199
|
+
| DatePart
|
|
2200
|
+
| list
|
|
2201
|
+
| ListWrapper[Any]
|
|
2110
2202
|
)
|
trilogy/core/models/build.py
CHANGED
|
@@ -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:
|
|
1501
|
-
|
|
1502
|
+
from trilogy.parsing.common import arbitrary_to_concept
|
|
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
|
|
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,
|
|
@@ -1639,9 +1652,26 @@ class Factory:
|
|
|
1639
1652
|
|
|
1640
1653
|
@build.register
|
|
1641
1654
|
def _(self, base: Comparison) -> BuildComparison:
|
|
1655
|
+
from trilogy.parsing.common import arbitrary_to_concept
|
|
1656
|
+
|
|
1657
|
+
left = base.left
|
|
1658
|
+
if isinstance(left, AggregateWrapper):
|
|
1659
|
+
left_c = arbitrary_to_concept(
|
|
1660
|
+
left,
|
|
1661
|
+
environment=self.environment,
|
|
1662
|
+
)
|
|
1663
|
+
left = left_c # type: ignore
|
|
1664
|
+
right = base.right
|
|
1665
|
+
if isinstance(right, AggregateWrapper):
|
|
1666
|
+
right_c = arbitrary_to_concept(
|
|
1667
|
+
right,
|
|
1668
|
+
environment=self.environment,
|
|
1669
|
+
)
|
|
1670
|
+
|
|
1671
|
+
right = right_c # type: ignore
|
|
1642
1672
|
return BuildComparison.model_construct(
|
|
1643
|
-
left=(self.build(
|
|
1644
|
-
right=(self.build(
|
|
1673
|
+
left=(self.build(left)),
|
|
1674
|
+
right=(self.build(right)),
|
|
1645
1675
|
operator=base.operator,
|
|
1646
1676
|
)
|
|
1647
1677
|
|
|
@@ -1660,7 +1690,7 @@ class Factory:
|
|
|
1660
1690
|
)
|
|
1661
1691
|
|
|
1662
1692
|
@build.register
|
|
1663
|
-
def _(self, base: RowsetItem):
|
|
1693
|
+
def _(self, base: RowsetItem) -> BuildRowsetItem:
|
|
1664
1694
|
|
|
1665
1695
|
factory = Factory(
|
|
1666
1696
|
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/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
|
-
|
|
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
|
@@ -46,6 +46,63 @@ 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,
|
|
88
|
+
(ListWrapper, MapWrapper),
|
|
89
|
+
):
|
|
90
|
+
id_hash = string_to_hash(str(arg))
|
|
91
|
+
name = f"{VIRTUAL_CONCEPT_PREFIX}_{id_hash}"
|
|
92
|
+
if f"{environment.namespace}.{name}" in environment.concepts:
|
|
93
|
+
return environment.concepts[f"{environment.namespace}.{name}"]
|
|
94
|
+
concept = arbitrary_to_concept(
|
|
95
|
+
arg,
|
|
96
|
+
name=name,
|
|
97
|
+
environment=environment,
|
|
98
|
+
)
|
|
99
|
+
if concept.metadata and meta:
|
|
100
|
+
concept.metadata.line_number = meta.line
|
|
101
|
+
environment.add_concept(concept, meta=meta)
|
|
102
|
+
return concept
|
|
103
|
+
return arg
|
|
104
|
+
|
|
105
|
+
|
|
49
106
|
def process_function_args(
|
|
50
107
|
args,
|
|
51
108
|
meta: Meta | None,
|
|
@@ -53,54 +110,7 @@ def process_function_args(
|
|
|
53
110
|
) -> List[Concept | Function | str | int | float | date | datetime]:
|
|
54
111
|
final: List[Concept | Function | str | int | float | date | datetime] = []
|
|
55
112
|
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)
|
|
113
|
+
final.append(process_function_arg(arg, meta, environment))
|
|
104
114
|
return final
|
|
105
115
|
|
|
106
116
|
|
|
@@ -240,6 +250,24 @@ def concepts_to_grain_concepts(
|
|
|
240
250
|
return v2
|
|
241
251
|
|
|
242
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
|
+
|
|
243
271
|
def function_to_concept(
|
|
244
272
|
parent: Function,
|
|
245
273
|
name: str,
|
|
@@ -247,14 +275,12 @@ def function_to_concept(
|
|
|
247
275
|
namespace: str | None = None,
|
|
248
276
|
metadata: Metadata | None = None,
|
|
249
277
|
) -> Concept:
|
|
278
|
+
|
|
250
279
|
pkeys: List[Concept] = []
|
|
251
280
|
namespace = namespace or environment.namespace
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if not isinstance(x, UndefinedConcept)
|
|
256
|
-
]
|
|
257
|
-
|
|
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]
|
|
258
284
|
pkeys += [x for x in concrete_args if not x.derivation == Derivation.CONSTANT]
|
|
259
285
|
grain: Grain | None = Grain()
|
|
260
286
|
for x in pkeys:
|
|
@@ -265,12 +291,18 @@ def function_to_concept(
|
|
|
265
291
|
modifiers = get_upstream_modifiers(pkeys, environment)
|
|
266
292
|
key_grain: list[str] = []
|
|
267
293
|
for x in pkeys:
|
|
268
|
-
|
|
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:
|
|
269
299
|
key_grain += [*x.keys]
|
|
270
300
|
else:
|
|
271
301
|
key_grain.append(x.address)
|
|
272
302
|
keys = set(key_grain)
|
|
273
|
-
if
|
|
303
|
+
if is_metric:
|
|
304
|
+
purpose = Purpose.METRIC
|
|
305
|
+
elif not pkeys:
|
|
274
306
|
purpose = Purpose.CONSTANT
|
|
275
307
|
else:
|
|
276
308
|
purpose = parent.output_purpose
|
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
|