pytrilogy 0.0.3.37__py3-none-any.whl → 0.0.3.38__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.37.dist-info → pytrilogy-0.0.3.38.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.3.37.dist-info → pytrilogy-0.0.3.38.dist-info}/RECORD +23 -23
- {pytrilogy-0.0.3.37.dist-info → pytrilogy-0.0.3.38.dist-info}/WHEEL +1 -1
- trilogy/__init__.py +1 -1
- trilogy/core/functions.py +1 -0
- trilogy/core/models/author.py +8 -2
- trilogy/core/models/build.py +54 -5
- trilogy/core/processing/concept_strategies_v3.py +23 -13
- trilogy/core/processing/node_generators/filter_node.py +3 -2
- trilogy/core/processing/node_generators/group_to_node.py +3 -0
- trilogy/core/processing/node_generators/union_node.py +44 -28
- trilogy/core/processing/nodes/__init__.py +19 -0
- trilogy/core/processing/nodes/merge_node.py +3 -0
- trilogy/core/processing/utility.py +30 -6
- trilogy/core/query_processor.py +3 -0
- trilogy/core/statements/author.py +2 -0
- trilogy/dialect/base.py +1 -0
- trilogy/parsing/common.py +79 -2
- trilogy/parsing/parse_engine.py +7 -0
- trilogy/parsing/trilogy.lark +9 -5
- {pytrilogy-0.0.3.37.dist-info → pytrilogy-0.0.3.38.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.3.37.dist-info → pytrilogy-0.0.3.38.dist-info}/licenses/LICENSE.md +0 -0
- {pytrilogy-0.0.3.37.dist-info → pytrilogy-0.0.3.38.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
pytrilogy-0.0.3.
|
|
2
|
-
trilogy/__init__.py,sha256=
|
|
1
|
+
pytrilogy-0.0.3.38.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
2
|
+
trilogy/__init__.py,sha256=qMCD2lyNbU_pQxwX1NltGMuBiXL8s_weV7CjLANmsDM,303
|
|
3
3
|
trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
trilogy/constants.py,sha256=5eQxk1A0pv-TQk3CCvgZCFA9_K-6nxrOm7E5Lxd7KIY,1652
|
|
5
5
|
trilogy/engine.py,sha256=OK2RuqCIUId6yZ5hfF8J1nxGP0AJqHRZiafcowmW0xc,1728
|
|
@@ -16,14 +16,14 @@ trilogy/core/env_processor.py,sha256=pFsxnluKIusGKx1z7tTnfsd_xZcPy9pZDungkjkyvI0
|
|
|
16
16
|
trilogy/core/environment_helpers.py,sha256=80XJrhSBd3CO7aWUc7r_oxXh6CeiR9cv0zaPyfK_uDM,8540
|
|
17
17
|
trilogy/core/ergonomics.py,sha256=e-7gE29vPLFdg0_A1smQ7eOrUwKl5VYdxRSTddHweRA,1631
|
|
18
18
|
trilogy/core/exceptions.py,sha256=JPYyBcit3T_pRtlHdtKSeVJkIyWUTozW2aaut25A2xI,673
|
|
19
|
-
trilogy/core/functions.py,sha256=
|
|
19
|
+
trilogy/core/functions.py,sha256=rU9Wc2HaIZEQC9L1iDKSAPeshheCOH0eEpBK1QR6JwE,27644
|
|
20
20
|
trilogy/core/graph_models.py,sha256=z17EoO8oky2QOuO6E2aMWoVNKEVJFhLdsQZOhC4fNLU,2079
|
|
21
21
|
trilogy/core/internal.py,sha256=iicDBlC6nM8d7e7jqzf_ZOmpUsW8yrr2AA8AqEiLx-s,1577
|
|
22
22
|
trilogy/core/optimization.py,sha256=aihzx4-2-mSjx5td1TDTYGvc7e9Zvy-_xEyhPqLS-Ig,8314
|
|
23
|
-
trilogy/core/query_processor.py,sha256=
|
|
23
|
+
trilogy/core/query_processor.py,sha256=Vl-u0F0rbqI2liv82yJgiZCB255Kx_KiuzZVHL6aeTM,19459
|
|
24
24
|
trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
-
trilogy/core/models/author.py,sha256=
|
|
26
|
-
trilogy/core/models/build.py,sha256=
|
|
25
|
+
trilogy/core/models/author.py,sha256=xV4l0mmX1HskrioE3LEuJbY6wKjvetUVepL447EZ6xQ,77142
|
|
26
|
+
trilogy/core/models/build.py,sha256=flDLLFCR0XJ0fpUhkGg-bpDOkH915tDdXaKmrAzNIcg,61828
|
|
27
27
|
trilogy/core/models/build_environment.py,sha256=s_C9xAHuD3yZ26T15pWVBvoqvlp2LdZ8yjsv2_HdXLk,5363
|
|
28
28
|
trilogy/core/models/core.py,sha256=wx6hJcFECMG-Ij972ADNkr-3nFXkYESr82ObPiC46_U,10875
|
|
29
29
|
trilogy/core/models/datasource.py,sha256=6RjJUd2u4nYmEwFBpJlM9LbHVYDv8iHJxqiBMZqUrwI,9422
|
|
@@ -35,42 +35,42 @@ trilogy/core/optimizations/inline_constant.py,sha256=lvNTIXaLNkw3HseJyXyDNk5R52d
|
|
|
35
35
|
trilogy/core/optimizations/inline_datasource.py,sha256=AHuTGh2x0GQ8usOe0NiFncfTFQ_KogdgDl4uucmhIbI,4241
|
|
36
36
|
trilogy/core/optimizations/predicate_pushdown.py,sha256=g4AYE8Aw_iMlAh68TjNXGP754NTurrDduFECkUjoBnc,9399
|
|
37
37
|
trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
-
trilogy/core/processing/concept_strategies_v3.py,sha256=
|
|
38
|
+
trilogy/core/processing/concept_strategies_v3.py,sha256=reXqgktFKy5qhm4k6abrs4J16Sovxd4naHrsEcNarEk,43957
|
|
39
39
|
trilogy/core/processing/graph_utils.py,sha256=8QUVrkE9j-9C1AyrCb1nQEh8daCe0u1HuXl-Te85lag,1205
|
|
40
|
-
trilogy/core/processing/utility.py,sha256=
|
|
40
|
+
trilogy/core/processing/utility.py,sha256=rfzdgl-vWkCyhLzXNNuWgPLK59eiYypQb6TdZKymUqk,21469
|
|
41
41
|
trilogy/core/processing/node_generators/__init__.py,sha256=o8rOFHPSo-s_59hREwXMW6gjUJCsiXumdbJNozHUf-Y,800
|
|
42
42
|
trilogy/core/processing/node_generators/basic_node.py,sha256=UVsXMn6jTjm_ofVFt218jAS11s4RV4zD781vP4im-GI,3371
|
|
43
43
|
trilogy/core/processing/node_generators/common.py,sha256=ZsDzThjm_mAtdQpKAg8QIJiPVZ4KuUkKyilj4eOhSDs,9439
|
|
44
|
-
trilogy/core/processing/node_generators/filter_node.py,sha256=
|
|
44
|
+
trilogy/core/processing/node_generators/filter_node.py,sha256=lT167yBgy3P9sDBM1Cjj0PKSXro8dvGtBmc8nwsUjig,8366
|
|
45
45
|
trilogy/core/processing/node_generators/group_node.py,sha256=kO-ersxIL04rZwX5-vFIFQQnp357PFo_7ZKXoGq3wyc,5989
|
|
46
|
-
trilogy/core/processing/node_generators/group_to_node.py,sha256=
|
|
46
|
+
trilogy/core/processing/node_generators/group_to_node.py,sha256=v2Xl5VjNO2hKJAdmqCbmAOqaRPvxRUbtfhhQXFMifWk,3197
|
|
47
47
|
trilogy/core/processing/node_generators/multiselect_node.py,sha256=GWV5yLmKTe1yyPhN60RG1Rnrn4ktfn9lYYXi_FVU4UI,7061
|
|
48
48
|
trilogy/core/processing/node_generators/node_merge_node.py,sha256=sv55oynfqgpHEpo1OEtVDri-5fywzPhDlR85qaWikvY,16195
|
|
49
49
|
trilogy/core/processing/node_generators/rowset_node.py,sha256=YmBs6ZQ7azLXRFEmeoecpGjK4pMHsUCovuBxfb3UKZI,6848
|
|
50
50
|
trilogy/core/processing/node_generators/select_merge_node.py,sha256=lxXhMhDKGbu67QFNbbAT-BO8gbWppIvjn_hAXpLEPe0,19953
|
|
51
51
|
trilogy/core/processing/node_generators/select_node.py,sha256=Y-zO0AFkTrpi2LyebjpyHU7WWANr7nKZSS9rY7DH4Wo,1888
|
|
52
52
|
trilogy/core/processing/node_generators/synonym_node.py,sha256=9LHK2XHDjbyTLjmDQieskG8fqbiSpRnFOkfrutDnOTE,2258
|
|
53
|
-
trilogy/core/processing/node_generators/union_node.py,sha256=
|
|
53
|
+
trilogy/core/processing/node_generators/union_node.py,sha256=VNo6Oey4p8etU9xrOh2oTT2lIOTvY6PULUPRvVa2uxU,2877
|
|
54
54
|
trilogy/core/processing/node_generators/unnest_node.py,sha256=cOEKnMRzXUW3bwmiOlgn3E1-B38osng0dh2pDykwITY,2410
|
|
55
55
|
trilogy/core/processing/node_generators/window_node.py,sha256=MjLmFKUiS-_p-Ak_9mr3becGde9eu5frxmqI7plIETY,5808
|
|
56
56
|
trilogy/core/processing/node_generators/select_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
57
57
|
trilogy/core/processing/node_generators/select_helpers/datasource_injection.py,sha256=GMW07bb6hXurhF0hZLYoMAKSIS65tat5hwBjvqqPeSA,6516
|
|
58
|
-
trilogy/core/processing/nodes/__init__.py,sha256=
|
|
58
|
+
trilogy/core/processing/nodes/__init__.py,sha256=Lxr3rs_bqOAtMtn3DHIkY058ZzjyLM5mSfGMKW2z0NY,5555
|
|
59
59
|
trilogy/core/processing/nodes/base_node.py,sha256=FHrY8GsTKPuMJklOjILbhGqCt5s1nmlj62Z-molARDA,16835
|
|
60
60
|
trilogy/core/processing/nodes/filter_node.py,sha256=5VtRfKbCORx0dV-vQfgy3gOEkmmscL9f31ExvlODwvY,2461
|
|
61
61
|
trilogy/core/processing/nodes/group_node.py,sha256=MUvcOg9U5J6TnWBel8eht9PdI9BfAKjUxmfjP_ZXx9o,10484
|
|
62
|
-
trilogy/core/processing/nodes/merge_node.py,sha256=
|
|
62
|
+
trilogy/core/processing/nodes/merge_node.py,sha256=02oWRca0ba41U6PSAB14jwnWWxoyrvxRPLwkli259SY,15865
|
|
63
63
|
trilogy/core/processing/nodes/select_node_v2.py,sha256=Xyfq8lU7rP7JTAd8VV0ATDNal64n4xIBgWQsOuMe_Ak,8824
|
|
64
64
|
trilogy/core/processing/nodes/union_node.py,sha256=fDFzLAUh5876X6_NM7nkhoMvHEdGJ_LpvPokpZKOhx4,1425
|
|
65
65
|
trilogy/core/processing/nodes/unnest_node.py,sha256=oLKMMNMx6PLDPlt2V5neFMFrFWxET8r6XZElAhSNkO0,2181
|
|
66
66
|
trilogy/core/processing/nodes/window_node.py,sha256=JXJ0iVRlSEM2IBr1TANym2RaUf_p5E_l2sNykRzXWDo,1710
|
|
67
67
|
trilogy/core/statements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
68
|
-
trilogy/core/statements/author.py,sha256=
|
|
68
|
+
trilogy/core/statements/author.py,sha256=rYDf9rCQ4YKEO9br1OWmOZd-51AiaDkaWYegvteJa8M,14728
|
|
69
69
|
trilogy/core/statements/build.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
70
70
|
trilogy/core/statements/common.py,sha256=KxEmz2ySySyZ6CTPzn0fJl5NX2KOk1RPyuUSwWhnK1g,759
|
|
71
71
|
trilogy/core/statements/execute.py,sha256=cSlvpHFOqpiZ89pPZ5GDp9Hu6j6uj-5_h21FWm_L-KM,1248
|
|
72
72
|
trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
73
|
-
trilogy/dialect/base.py,sha256=
|
|
73
|
+
trilogy/dialect/base.py,sha256=vp6_9fUkblAWVpCXGBIcoAx6N7vof9M7s9t6-b_waUY,41409
|
|
74
74
|
trilogy/dialect/bigquery.py,sha256=j5PQvwMUMcLHaxZgbqe6P-v-pwhHDQ38z8uK6ecxzR0,3359
|
|
75
75
|
trilogy/dialect/common.py,sha256=XjHkP8Dqezjkd2JU5xoAlMRS_6HNyXQCF4CykLK3C8o,5011
|
|
76
76
|
trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
|
|
@@ -87,13 +87,13 @@ trilogy/hooks/graph_hook.py,sha256=c-vC-IXoJ_jDmKQjxQyIxyXPOuUcLIURB573gCsAfzQ,2
|
|
|
87
87
|
trilogy/hooks/query_debugger.py,sha256=1npRjww94sPV5RRBBlLqMJRaFkH9vhEY6o828MeoEcw,5583
|
|
88
88
|
trilogy/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
89
89
|
trilogy/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
90
|
-
trilogy/parsing/common.py,sha256=
|
|
90
|
+
trilogy/parsing/common.py,sha256=ksmz25YM0NoRfGe6vd5aIY5jb6ehVxAeWrPqeV1byvs,25851
|
|
91
91
|
trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
|
|
92
92
|
trilogy/parsing/exceptions.py,sha256=92E5i2frv5hj9wxObJZsZqj5T6bglvPzvdvco_vW1Zk,38
|
|
93
93
|
trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
94
|
-
trilogy/parsing/parse_engine.py,sha256=
|
|
94
|
+
trilogy/parsing/parse_engine.py,sha256=1UAz-F_yDNbkJUCl7yw7lrf_7dYdgE0pkLO5IoxKuWE,64090
|
|
95
95
|
trilogy/parsing/render.py,sha256=hI4y-xjXrEXvHslY2l2TQ8ic0zAOpN41ADH37J2_FZY,19047
|
|
96
|
-
trilogy/parsing/trilogy.lark,sha256=
|
|
96
|
+
trilogy/parsing/trilogy.lark,sha256=doS_ADa58Aypqwhmq9xXzJYZhhTXduOpeiaAsIS3QkA,13367
|
|
97
97
|
trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
98
98
|
trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
|
|
99
99
|
trilogy/std/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -102,8 +102,8 @@ trilogy/std/display.preql,sha256=2BbhvqR4rcltyAbOXAUo7SZ_yGFYZgFnurglHMbjW2g,40
|
|
|
102
102
|
trilogy/std/geography.preql,sha256=-fqAGnBL6tR-UtT8DbSek3iMFg66ECR_B_41pODxv-k,504
|
|
103
103
|
trilogy/std/money.preql,sha256=ZHW-csTX-kYbOLmKSO-TcGGgQ-_DMrUXy0BjfuJSFxM,80
|
|
104
104
|
trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
|
|
105
|
-
pytrilogy-0.0.3.
|
|
106
|
-
pytrilogy-0.0.3.
|
|
107
|
-
pytrilogy-0.0.3.
|
|
108
|
-
pytrilogy-0.0.3.
|
|
109
|
-
pytrilogy-0.0.3.
|
|
105
|
+
pytrilogy-0.0.3.38.dist-info/METADATA,sha256=7N_2ajwk3_pT4c2GSBO9CNkHsNVGXMIYRVIf5hRxq8g,9100
|
|
106
|
+
pytrilogy-0.0.3.38.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
|
107
|
+
pytrilogy-0.0.3.38.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
|
|
108
|
+
pytrilogy-0.0.3.38.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
109
|
+
pytrilogy-0.0.3.38.dist-info/RECORD,,
|
trilogy/__init__.py
CHANGED
trilogy/core/functions.py
CHANGED
|
@@ -191,6 +191,7 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
|
|
|
191
191
|
),
|
|
192
192
|
FunctionType.GROUP: FunctionConfig(
|
|
193
193
|
arg_count=-1,
|
|
194
|
+
output_type_function=lambda args: get_output_type_at_index(args, 0),
|
|
194
195
|
),
|
|
195
196
|
FunctionType.COUNT: FunctionConfig(
|
|
196
197
|
output_purpose=Purpose.METRIC,
|
trilogy/core/models/author.py
CHANGED
|
@@ -180,8 +180,14 @@ class UndefinedConcept(ConceptRef):
|
|
|
180
180
|
|
|
181
181
|
def address_with_namespace(address: str, namespace: str) -> str:
|
|
182
182
|
existing_ns = address.split(".", 1)[0]
|
|
183
|
+
if "." in address:
|
|
184
|
+
existing_name = address.split(".", 1)[1]
|
|
185
|
+
else:
|
|
186
|
+
existing_name = address
|
|
187
|
+
if existing_name == ALL_ROWS_CONCEPT:
|
|
188
|
+
return address
|
|
183
189
|
if existing_ns == DEFAULT_NAMESPACE:
|
|
184
|
-
return f"{namespace}.{
|
|
190
|
+
return f"{namespace}.{existing_name}"
|
|
185
191
|
return f"{namespace}.{address}"
|
|
186
192
|
|
|
187
193
|
|
|
@@ -1129,7 +1135,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
|
|
|
1129
1135
|
return self.lineage.concept_arguments if self.lineage else []
|
|
1130
1136
|
|
|
1131
1137
|
@classmethod
|
|
1132
|
-
def calculate_derivation(self, lineage, purpose):
|
|
1138
|
+
def calculate_derivation(self, lineage, purpose: Purpose) -> Derivation:
|
|
1133
1139
|
from trilogy.core.models.build import (
|
|
1134
1140
|
BuildAggregateWrapper,
|
|
1135
1141
|
BuildFilterItem,
|
trilogy/core/models/build.py
CHANGED
|
@@ -65,6 +65,7 @@ from trilogy.core.models.author import (
|
|
|
65
65
|
RowsetLineage,
|
|
66
66
|
SelectLineage,
|
|
67
67
|
SubselectComparison,
|
|
68
|
+
UndefinedConcept,
|
|
68
69
|
WhereClause,
|
|
69
70
|
WindowItem,
|
|
70
71
|
)
|
|
@@ -800,9 +801,6 @@ class BuildConcept(Addressable, BuildConceptArgs, DataTyped, BaseModel):
|
|
|
800
801
|
modifiers: List[Modifier] = Field(default_factory=list) # type: ignore
|
|
801
802
|
pseudonyms: set[str] = Field(default_factory=set)
|
|
802
803
|
|
|
803
|
-
def with_select_context(self, *args, **kwargs):
|
|
804
|
-
return self
|
|
805
|
-
|
|
806
804
|
@property
|
|
807
805
|
def is_aggregate(self) -> bool:
|
|
808
806
|
return self.build_is_aggregate
|
|
@@ -1503,7 +1501,7 @@ class Factory:
|
|
|
1503
1501
|
return base
|
|
1504
1502
|
|
|
1505
1503
|
@build.register
|
|
1506
|
-
def _(self, base: Function) -> BuildFunction:
|
|
1504
|
+
def _(self, base: Function) -> BuildFunction | BuildAggregateWrapper:
|
|
1507
1505
|
from trilogy.parsing.common import arbitrary_to_concept
|
|
1508
1506
|
|
|
1509
1507
|
raw_args: list[Concept | FuncArgs] = []
|
|
@@ -1517,6 +1515,54 @@ class Factory:
|
|
|
1517
1515
|
raw_args.append(narg)
|
|
1518
1516
|
else:
|
|
1519
1517
|
raw_args.append(arg)
|
|
1518
|
+
if base.operator == FunctionType.GROUP:
|
|
1519
|
+
group_base = raw_args[0]
|
|
1520
|
+
final_args: List[Concept | ConceptRef] = []
|
|
1521
|
+
if isinstance(group_base, ConceptRef):
|
|
1522
|
+
if group_base.address in self.environment.concepts and not isinstance(
|
|
1523
|
+
self.environment.concepts[group_base.address], UndefinedConcept
|
|
1524
|
+
):
|
|
1525
|
+
group_base = self.environment.concepts[group_base.address]
|
|
1526
|
+
if (
|
|
1527
|
+
isinstance(group_base, Concept)
|
|
1528
|
+
and isinstance(group_base.lineage, AggregateWrapper)
|
|
1529
|
+
and not group_base.lineage.by
|
|
1530
|
+
):
|
|
1531
|
+
arguments = raw_args[1:]
|
|
1532
|
+
for x in arguments:
|
|
1533
|
+
if isinstance(x, (ConceptRef, Concept)):
|
|
1534
|
+
final_args.append(x)
|
|
1535
|
+
elif isinstance(x, (AggregateWrapper, FilterItem, WindowItem)):
|
|
1536
|
+
newx = arbitrary_to_concept(
|
|
1537
|
+
x,
|
|
1538
|
+
environment=self.environment,
|
|
1539
|
+
)
|
|
1540
|
+
final_args.append(newx)
|
|
1541
|
+
else:
|
|
1542
|
+
# constants, etc, can be ignored for group
|
|
1543
|
+
continue
|
|
1544
|
+
group_base = group_base.model_copy(
|
|
1545
|
+
deep=True,
|
|
1546
|
+
update={
|
|
1547
|
+
"lineage": AggregateWrapper(
|
|
1548
|
+
function=group_base.lineage.function,
|
|
1549
|
+
by=final_args,
|
|
1550
|
+
)
|
|
1551
|
+
},
|
|
1552
|
+
)
|
|
1553
|
+
group_base = group_base.with_grain(
|
|
1554
|
+
Grain.from_concepts(final_args, environment=self.environment)
|
|
1555
|
+
)
|
|
1556
|
+
rval = self.build(group_base)
|
|
1557
|
+
return BuildFunction.model_construct(
|
|
1558
|
+
operator=base.operator,
|
|
1559
|
+
arguments=[rval, *[self.build(c) for c in raw_args[1:]]],
|
|
1560
|
+
output_datatype=base.output_datatype,
|
|
1561
|
+
output_purpose=base.output_purpose,
|
|
1562
|
+
valid_inputs=base.valid_inputs,
|
|
1563
|
+
arg_count=base.arg_count,
|
|
1564
|
+
)
|
|
1565
|
+
|
|
1520
1566
|
new = BuildFunction.model_construct(
|
|
1521
1567
|
operator=base.operator,
|
|
1522
1568
|
arguments=[self.build(c) for c in raw_args],
|
|
@@ -1568,7 +1614,10 @@ class Factory:
|
|
|
1568
1614
|
new_lineage, final_grain, _ = base.get_select_grain_and_keys(
|
|
1569
1615
|
self.grain, self.environment
|
|
1570
1616
|
)
|
|
1571
|
-
|
|
1617
|
+
if new_lineage:
|
|
1618
|
+
build_lineage = self.build(new_lineage)
|
|
1619
|
+
else:
|
|
1620
|
+
build_lineage = None
|
|
1572
1621
|
derivation = Concept.calculate_derivation(build_lineage, base.purpose)
|
|
1573
1622
|
granularity = Concept.calculate_granularity(
|
|
1574
1623
|
derivation, final_grain, build_lineage
|
|
@@ -110,11 +110,14 @@ def get_priority_concept(
|
|
|
110
110
|
depth: int,
|
|
111
111
|
) -> BuildConcept:
|
|
112
112
|
# optimized search for missing concepts
|
|
113
|
-
pass_one =
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
113
|
+
pass_one = sorted(
|
|
114
|
+
[
|
|
115
|
+
c
|
|
116
|
+
for c in all_concepts
|
|
117
|
+
if c.address not in attempted_addresses and c.address not in found_concepts
|
|
118
|
+
],
|
|
119
|
+
key=lambda x: x.address,
|
|
120
|
+
)
|
|
118
121
|
# sometimes we need to scan intermediate concepts to get merge keys or filter keys,
|
|
119
122
|
# so do an exhaustive search
|
|
120
123
|
# pass_two = [c for c in all_concepts+filter_only if c.address not in attempted_addresses]
|
|
@@ -388,6 +391,9 @@ def generate_node(
|
|
|
388
391
|
# conditions=conditions,
|
|
389
392
|
)
|
|
390
393
|
else:
|
|
394
|
+
logger.info(
|
|
395
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} skipping search, already in a recursion fot these concepts"
|
|
396
|
+
)
|
|
391
397
|
return None
|
|
392
398
|
return ConstantNode(
|
|
393
399
|
input_concepts=[],
|
|
@@ -453,9 +459,10 @@ def generate_node(
|
|
|
453
459
|
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} including any filters, there are non-root concepts we should expand first: {non_root}. Recursing with all of these as mandatory"
|
|
454
460
|
)
|
|
455
461
|
|
|
456
|
-
if not history.check_started(
|
|
457
|
-
|
|
458
|
-
):
|
|
462
|
+
# if not history.check_started(
|
|
463
|
+
# root_targets, accept_partial=accept_partial, conditions=conditions
|
|
464
|
+
# ) or 1==1:
|
|
465
|
+
if True:
|
|
459
466
|
history.log_start(
|
|
460
467
|
root_targets, accept_partial=accept_partial, conditions=conditions
|
|
461
468
|
)
|
|
@@ -470,7 +477,10 @@ def generate_node(
|
|
|
470
477
|
# which we do whenever we hit a root node
|
|
471
478
|
# conditions=conditions,
|
|
472
479
|
)
|
|
473
|
-
|
|
480
|
+
else:
|
|
481
|
+
logger.info(
|
|
482
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} skipping root search, already in a recursion for these concepts"
|
|
483
|
+
)
|
|
474
484
|
check = history.gen_select_node(
|
|
475
485
|
concept,
|
|
476
486
|
local_optional,
|
|
@@ -546,7 +556,10 @@ def generate_node(
|
|
|
546
556
|
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} resolved concepts through synonyms"
|
|
547
557
|
)
|
|
548
558
|
return resolved
|
|
549
|
-
|
|
559
|
+
else:
|
|
560
|
+
logger.info(
|
|
561
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} skipping synonym search, already in a recursion for these concepts"
|
|
562
|
+
)
|
|
550
563
|
return None
|
|
551
564
|
else:
|
|
552
565
|
raise ValueError(f"Unknown derivation {concept.derivation} on {concept}")
|
|
@@ -1041,7 +1054,6 @@ def _search_concepts(
|
|
|
1041
1054
|
environment=environment,
|
|
1042
1055
|
depth=depth,
|
|
1043
1056
|
)
|
|
1044
|
-
logger.info(f"gcheck result is {result}")
|
|
1045
1057
|
if result.required:
|
|
1046
1058
|
logger.info(
|
|
1047
1059
|
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Adding group node"
|
|
@@ -1104,7 +1116,6 @@ def source_query_concepts(
|
|
|
1104
1116
|
history=history,
|
|
1105
1117
|
conditions=conditions,
|
|
1106
1118
|
)
|
|
1107
|
-
|
|
1108
1119
|
if not root:
|
|
1109
1120
|
error_strings = [
|
|
1110
1121
|
f"{c.address}<{c.purpose}>{c.derivation}>" for c in output_concepts
|
|
@@ -1130,5 +1141,4 @@ def source_query_concepts(
|
|
|
1130
1141
|
)
|
|
1131
1142
|
else:
|
|
1132
1143
|
candidate = root
|
|
1133
|
-
|
|
1134
1144
|
return candidate
|
|
@@ -48,9 +48,10 @@ def gen_filter_node(
|
|
|
48
48
|
if isinstance(x.lineage, FILTER_TYPES):
|
|
49
49
|
if concept.lineage.where == where:
|
|
50
50
|
logger.info(
|
|
51
|
-
f"{padding(depth)}{LOGGER_PREFIX} fetching {x.lineage.content.address} as optional parent with same filter conditions "
|
|
51
|
+
f"{padding(depth)}{LOGGER_PREFIX} fetching {x.lineage.content.address} as optional parent from optional {x} with same filter conditions "
|
|
52
52
|
)
|
|
53
|
-
|
|
53
|
+
if x.lineage.content.address not in parent_row_concepts:
|
|
54
|
+
parent_row_concepts.append(x.lineage.content)
|
|
54
55
|
optional_included.append(x)
|
|
55
56
|
continue
|
|
56
57
|
if conditions and conditions == where:
|
|
@@ -3,6 +3,8 @@ from typing import List
|
|
|
3
3
|
from trilogy.constants import logger
|
|
4
4
|
from trilogy.core.models.build import BuildConcept, BuildFunction, BuildWhereClause
|
|
5
5
|
from trilogy.core.models.build_environment import BuildEnvironment
|
|
6
|
+
|
|
7
|
+
# C:\Users\ethan\coding_projects\pytrilogy\trilogy\core\processing\node_generators\group_to_node.py
|
|
6
8
|
from trilogy.core.processing.nodes import (
|
|
7
9
|
GroupNode,
|
|
8
10
|
History,
|
|
@@ -30,6 +32,7 @@ def gen_group_to_node(
|
|
|
30
32
|
f"Group to should have function lineage, is {type(concept.lineage)}"
|
|
31
33
|
)
|
|
32
34
|
group_arg = concept.lineage.arguments[0]
|
|
35
|
+
|
|
33
36
|
parent_concepts: List[BuildConcept] = concept.lineage.concept_arguments
|
|
34
37
|
logger.info(
|
|
35
38
|
f"{padding(depth)}{LOGGER_PREFIX} group by node has required parents {[x.address for x in parent_concepts]}"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
3
|
from trilogy.constants import logger
|
|
4
|
-
from trilogy.core.enums import FunctionType
|
|
4
|
+
from trilogy.core.enums import FunctionType
|
|
5
5
|
from trilogy.core.models.build import BuildConcept, BuildFunction, BuildWhereClause
|
|
6
6
|
from trilogy.core.processing.nodes import History, StrategyNode, UnionNode
|
|
7
7
|
from trilogy.core.processing.utility import padding
|
|
@@ -16,6 +16,38 @@ def is_union(c: BuildConcept):
|
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
def build_layers(
|
|
20
|
+
concepts: list[BuildConcept],
|
|
21
|
+
) -> tuple[list[list[BuildConcept]], list[BuildConcept]]:
|
|
22
|
+
sources = {
|
|
23
|
+
x.address: x.lineage.concept_arguments if x.lineage else [] for x in concepts
|
|
24
|
+
}
|
|
25
|
+
root = concepts[0]
|
|
26
|
+
|
|
27
|
+
built_layers = []
|
|
28
|
+
layers = root.lineage.concept_arguments if root.lineage else []
|
|
29
|
+
sourced = set()
|
|
30
|
+
while layers:
|
|
31
|
+
layer = []
|
|
32
|
+
current = layers.pop()
|
|
33
|
+
sourced.add(current.address)
|
|
34
|
+
layer.append(current)
|
|
35
|
+
for key, values in sources.items():
|
|
36
|
+
if key == current.address:
|
|
37
|
+
continue
|
|
38
|
+
for value in values:
|
|
39
|
+
if value.address in (current.keys or []) or current.address in (
|
|
40
|
+
value.keys or []
|
|
41
|
+
):
|
|
42
|
+
layer.append(value)
|
|
43
|
+
sourced.add(value.address)
|
|
44
|
+
built_layers.append(layer)
|
|
45
|
+
complete = [
|
|
46
|
+
x for x in concepts if all([x.address in sourced for x in sources[x.address]])
|
|
47
|
+
]
|
|
48
|
+
return built_layers, complete
|
|
49
|
+
|
|
50
|
+
|
|
19
51
|
def gen_union_node(
|
|
20
52
|
concept: BuildConcept,
|
|
21
53
|
local_optional: List[BuildConcept],
|
|
@@ -27,27 +59,15 @@ def gen_union_node(
|
|
|
27
59
|
conditions: BuildWhereClause | None = None,
|
|
28
60
|
) -> StrategyNode | None:
|
|
29
61
|
all_unions = [x for x in local_optional if is_union(x)] + [concept]
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
remaining = [x for x in all_unions if x.address != base.address]
|
|
35
|
-
arguments = []
|
|
36
|
-
if isinstance(base.lineage, BuildFunction):
|
|
37
|
-
arguments = base.lineage.concept_arguments
|
|
38
|
-
for arg in arguments:
|
|
39
|
-
relevant_parents: list[BuildConcept] = []
|
|
40
|
-
for other_union in remaining:
|
|
41
|
-
assert other_union.lineage
|
|
42
|
-
potential_parents = [z for z in other_union.lineage.concept_arguments]
|
|
43
|
-
relevant_parents += [
|
|
44
|
-
x for x in potential_parents if x.keys and arg.address in x.keys
|
|
45
|
-
]
|
|
62
|
+
logger.info(f"{padding(depth)}{LOGGER_PREFIX} found unions {all_unions}")
|
|
63
|
+
parent_nodes = []
|
|
64
|
+
layers, resolved = build_layers(all_unions)
|
|
65
|
+
for layer in layers:
|
|
46
66
|
logger.info(
|
|
47
|
-
f"
|
|
67
|
+
f"{padding(depth)}{LOGGER_PREFIX} fetching layer {layer} with resolved {resolved}"
|
|
48
68
|
)
|
|
49
69
|
parent: StrategyNode = source_concepts(
|
|
50
|
-
mandatory_list=
|
|
70
|
+
mandatory_list=layer,
|
|
51
71
|
environment=environment,
|
|
52
72
|
g=g,
|
|
53
73
|
depth=depth + 1,
|
|
@@ -55,12 +75,8 @@ def gen_union_node(
|
|
|
55
75
|
conditions=conditions,
|
|
56
76
|
)
|
|
57
77
|
parent.hide_output_concepts(parent.output_concepts)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
for x in remaining:
|
|
61
|
-
parent.add_output_concept(x)
|
|
62
|
-
|
|
63
|
-
parents.append(parent)
|
|
78
|
+
parent.add_output_concepts(resolved)
|
|
79
|
+
parent_nodes.append(parent)
|
|
64
80
|
if not parent:
|
|
65
81
|
logger.info(
|
|
66
82
|
f"{padding(depth)}{LOGGER_PREFIX} could not find union node parents"
|
|
@@ -68,8 +84,8 @@ def gen_union_node(
|
|
|
68
84
|
return None
|
|
69
85
|
|
|
70
86
|
return UnionNode(
|
|
71
|
-
input_concepts=
|
|
72
|
-
output_concepts=
|
|
87
|
+
input_concepts=resolved,
|
|
88
|
+
output_concepts=resolved,
|
|
73
89
|
environment=environment,
|
|
74
|
-
parents=
|
|
90
|
+
parents=parent_nodes,
|
|
75
91
|
)
|
|
@@ -45,6 +45,11 @@ class History(BaseModel):
|
|
|
45
45
|
self.history[
|
|
46
46
|
self._concepts_to_lookup(search, accept_partial, conditions=conditions)
|
|
47
47
|
] = output
|
|
48
|
+
self.log_end(
|
|
49
|
+
search,
|
|
50
|
+
accept_partial=accept_partial,
|
|
51
|
+
conditions=conditions,
|
|
52
|
+
)
|
|
48
53
|
|
|
49
54
|
def get_history(
|
|
50
55
|
self,
|
|
@@ -83,6 +88,20 @@ class History(BaseModel):
|
|
|
83
88
|
)
|
|
84
89
|
)
|
|
85
90
|
|
|
91
|
+
def log_end(
|
|
92
|
+
self,
|
|
93
|
+
search: list[BuildConcept],
|
|
94
|
+
accept_partial: bool = False,
|
|
95
|
+
conditions: BuildWhereClause | None = None,
|
|
96
|
+
):
|
|
97
|
+
self.started.discard(
|
|
98
|
+
self._concepts_to_lookup(
|
|
99
|
+
search,
|
|
100
|
+
accept_partial=accept_partial,
|
|
101
|
+
conditions=conditions,
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
|
|
86
105
|
def check_started(
|
|
87
106
|
self,
|
|
88
107
|
search: list[BuildConcept],
|
|
@@ -272,6 +272,8 @@ class MergeNode(StrategyNode):
|
|
|
272
272
|
f"{self.logging_prefix}{LOGGER_PREFIX} Merge node has only one parent with the same"
|
|
273
273
|
" outputs as this merge node, dropping merge node "
|
|
274
274
|
)
|
|
275
|
+
# push up any conditions we need
|
|
276
|
+
final.ordering = self.ordering
|
|
275
277
|
return final
|
|
276
278
|
|
|
277
279
|
# if we have multiple candidates, see if one is good enough
|
|
@@ -293,6 +295,7 @@ class MergeNode(StrategyNode):
|
|
|
293
295
|
f" has all required output properties with partial {[c.address for c in dataset.partial_concepts]}"
|
|
294
296
|
f" and self has no conditions ({self.conditions})"
|
|
295
297
|
)
|
|
298
|
+
dataset.ordering = self.ordering
|
|
296
299
|
return dataset
|
|
297
300
|
|
|
298
301
|
pregrain = BuildGrain()
|
|
@@ -603,15 +603,29 @@ def find_nullable_concepts(
|
|
|
603
603
|
def sort_select_output_processed(
|
|
604
604
|
cte: CTE | UnionCTE, query: ProcessedQuery
|
|
605
605
|
) -> CTE | UnionCTE:
|
|
606
|
-
output_addresses = [
|
|
607
|
-
c.address for c in query.output_columns if c.address not in query.hidden_columns
|
|
608
|
-
]
|
|
606
|
+
output_addresses = [c.address for c in query.output_columns]
|
|
609
607
|
|
|
610
608
|
mapping = {x.address: x for x in cte.output_columns}
|
|
611
609
|
|
|
612
|
-
new_output = []
|
|
610
|
+
new_output: list[BuildConcept] = []
|
|
613
611
|
for x in output_addresses:
|
|
614
612
|
new_output.append(mapping[x])
|
|
613
|
+
|
|
614
|
+
for oc in cte.output_columns:
|
|
615
|
+
# add hidden back
|
|
616
|
+
if oc.address not in output_addresses:
|
|
617
|
+
new_output.append(oc)
|
|
618
|
+
|
|
619
|
+
cte.hidden_concepts = set(
|
|
620
|
+
[
|
|
621
|
+
c.address
|
|
622
|
+
for c in cte.output_columns
|
|
623
|
+
if (
|
|
624
|
+
c.address not in query.output_columns
|
|
625
|
+
or c.address in query.hidden_columns
|
|
626
|
+
)
|
|
627
|
+
]
|
|
628
|
+
)
|
|
615
629
|
cte.output_columns = new_output
|
|
616
630
|
return cte
|
|
617
631
|
|
|
@@ -619,18 +633,28 @@ def sort_select_output_processed(
|
|
|
619
633
|
def sort_select_output(
|
|
620
634
|
cte: CTE | UnionCTE, query: SelectStatement | MultiSelectStatement | ProcessedQuery
|
|
621
635
|
) -> CTE | UnionCTE:
|
|
636
|
+
|
|
622
637
|
if isinstance(query, ProcessedQuery):
|
|
623
638
|
return sort_select_output_processed(cte, query)
|
|
639
|
+
|
|
624
640
|
output_addresses = [
|
|
625
641
|
c.address
|
|
626
642
|
for c in query.output_components
|
|
627
|
-
if c.address not in query.hidden_components
|
|
643
|
+
# if c.address not in query.hidden_components
|
|
628
644
|
]
|
|
629
645
|
|
|
630
646
|
mapping = {x.address: x for x in cte.output_columns}
|
|
631
647
|
|
|
632
|
-
new_output = []
|
|
648
|
+
new_output: list[BuildConcept] = []
|
|
633
649
|
for x in output_addresses:
|
|
634
650
|
new_output.append(mapping[x])
|
|
635
651
|
cte.output_columns = new_output
|
|
652
|
+
cte.hidden_concepts = set(
|
|
653
|
+
[
|
|
654
|
+
c.address
|
|
655
|
+
for c in query.output_components
|
|
656
|
+
if c.address in query.hidden_components
|
|
657
|
+
]
|
|
658
|
+
)
|
|
659
|
+
|
|
636
660
|
return cte
|
trilogy/core/query_processor.py
CHANGED
|
@@ -431,7 +431,9 @@ def get_query_datasources(
|
|
|
431
431
|
hooks: Optional[List[BaseHook]] = None,
|
|
432
432
|
) -> QueryDatasource:
|
|
433
433
|
ds = get_query_node(environment, statement.as_lineage(environment))
|
|
434
|
+
|
|
434
435
|
final_qds = ds.resolve()
|
|
436
|
+
|
|
435
437
|
if hooks:
|
|
436
438
|
for hook in hooks:
|
|
437
439
|
hook.process_root_strategy_node(ds)
|
|
@@ -510,6 +512,7 @@ def process_query(
|
|
|
510
512
|
hook.process_root_datasource(root_datasource)
|
|
511
513
|
# this should always return 1 - TODO, refactor
|
|
512
514
|
root_cte = datasource_to_cte(root_datasource, environment.cte_name_map)
|
|
515
|
+
|
|
513
516
|
for hook in hooks:
|
|
514
517
|
hook.process_root_cte(root_cte)
|
|
515
518
|
raw_ctes: List[CTE | UnionCTE] = list(reversed(flatten_ctes(root_cte)))
|
|
@@ -135,6 +135,7 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
|
|
|
135
135
|
)
|
|
136
136
|
|
|
137
137
|
output.grain = output.calculate_grain(environment)
|
|
138
|
+
|
|
138
139
|
for x in selection:
|
|
139
140
|
if x.is_undefined and environment.concepts.fail_on_missing:
|
|
140
141
|
environment.concepts.raise_undefined(
|
|
@@ -166,6 +167,7 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
|
|
|
166
167
|
targets = []
|
|
167
168
|
for x in self.selection:
|
|
168
169
|
targets.append(x.concept)
|
|
170
|
+
|
|
169
171
|
result = Grain.from_concepts(
|
|
170
172
|
targets, where_clause=self.where_clause, environment=environment
|
|
171
173
|
)
|
trilogy/dialect/base.py
CHANGED
|
@@ -171,6 +171,7 @@ FUNCTION_MAP = {
|
|
|
171
171
|
FunctionType.DATETIME_LITERAL: lambda x: f"datetime '{x}'",
|
|
172
172
|
# math
|
|
173
173
|
FunctionType.ADD: lambda x: " + ".join(x),
|
|
174
|
+
FunctionType.ABS: lambda x: f"abs({x[0]})",
|
|
174
175
|
FunctionType.SUBTRACT: lambda x: " - ".join(x),
|
|
175
176
|
FunctionType.DIVIDE: lambda x: " / ".join(x),
|
|
176
177
|
FunctionType.MULTIPLY: lambda x: " * ".join(x),
|
trilogy/parsing/common.py
CHANGED
|
@@ -220,7 +220,6 @@ def concept_is_relevant(
|
|
|
220
220
|
if all([c in others for c in concept.grain.components]):
|
|
221
221
|
return False
|
|
222
222
|
if concept.derivation in (Derivation.BASIC,):
|
|
223
|
-
|
|
224
223
|
return any(
|
|
225
224
|
concept_is_relevant(c, others, environment)
|
|
226
225
|
for c in concept.concept_arguments
|
|
@@ -235,6 +234,7 @@ def concepts_to_grain_concepts(
|
|
|
235
234
|
) -> list[Concept]:
|
|
236
235
|
pconcepts: list[Concept] = []
|
|
237
236
|
for c in concepts:
|
|
237
|
+
|
|
238
238
|
if isinstance(c, Concept):
|
|
239
239
|
pconcepts.append(c)
|
|
240
240
|
elif isinstance(c, ConceptRef) and environment:
|
|
@@ -281,6 +281,76 @@ def get_relevant_parent_concepts(arg):
|
|
|
281
281
|
return results
|
|
282
282
|
|
|
283
283
|
|
|
284
|
+
def group_function_to_concept(
|
|
285
|
+
parent: Function,
|
|
286
|
+
name: str,
|
|
287
|
+
environment: Environment,
|
|
288
|
+
namespace: str | None = None,
|
|
289
|
+
metadata: Metadata | None = None,
|
|
290
|
+
):
|
|
291
|
+
pkeys: List[Concept] = []
|
|
292
|
+
namespace = namespace or environment.namespace
|
|
293
|
+
is_metric = False
|
|
294
|
+
ref_args, is_metric = get_relevant_parent_concepts(parent)
|
|
295
|
+
concrete_args = [environment.concepts[c.address] for c in ref_args]
|
|
296
|
+
pkeys += [x for x in concrete_args if not x.derivation == Derivation.CONSTANT]
|
|
297
|
+
modifiers = get_upstream_modifiers(pkeys, environment)
|
|
298
|
+
key_grain: list[str] = []
|
|
299
|
+
for x in pkeys:
|
|
300
|
+
# for a group to, if we have a dynamic metric, ignore it
|
|
301
|
+
# it will end up with the group target grain
|
|
302
|
+
if x.purpose == Purpose.METRIC and not x.keys:
|
|
303
|
+
continue
|
|
304
|
+
# metrics will group to keys, so do no do key traversal
|
|
305
|
+
elif is_metric:
|
|
306
|
+
key_grain.append(x.address)
|
|
307
|
+
else:
|
|
308
|
+
key_grain.append(x.address)
|
|
309
|
+
keys = set(key_grain)
|
|
310
|
+
|
|
311
|
+
grain = Grain.from_concepts(keys, environment)
|
|
312
|
+
if is_metric:
|
|
313
|
+
purpose = Purpose.METRIC
|
|
314
|
+
elif not pkeys:
|
|
315
|
+
purpose = Purpose.CONSTANT
|
|
316
|
+
else:
|
|
317
|
+
purpose = parent.output_purpose
|
|
318
|
+
fmetadata = metadata or Metadata()
|
|
319
|
+
granularity = Granularity.MULTI_ROW
|
|
320
|
+
|
|
321
|
+
if grain is not None:
|
|
322
|
+
# deduplicte
|
|
323
|
+
grain = Grain.from_concepts(grain.components, environment)
|
|
324
|
+
|
|
325
|
+
r = Concept(
|
|
326
|
+
name=name,
|
|
327
|
+
datatype=parent.output_datatype,
|
|
328
|
+
purpose=purpose,
|
|
329
|
+
lineage=parent,
|
|
330
|
+
namespace=namespace,
|
|
331
|
+
keys=keys,
|
|
332
|
+
modifiers=modifiers,
|
|
333
|
+
grain=grain,
|
|
334
|
+
metadata=fmetadata,
|
|
335
|
+
derivation=Derivation.BASIC,
|
|
336
|
+
granularity=granularity,
|
|
337
|
+
)
|
|
338
|
+
return r
|
|
339
|
+
|
|
340
|
+
return Concept(
|
|
341
|
+
name=name,
|
|
342
|
+
datatype=parent.output_datatype,
|
|
343
|
+
purpose=purpose,
|
|
344
|
+
lineage=parent,
|
|
345
|
+
namespace=namespace,
|
|
346
|
+
keys=keys,
|
|
347
|
+
modifiers=modifiers,
|
|
348
|
+
metadata=fmetadata,
|
|
349
|
+
derivation=Derivation.BASIC,
|
|
350
|
+
granularity=granularity,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
|
|
284
354
|
def function_to_concept(
|
|
285
355
|
parent: Function,
|
|
286
356
|
name: str,
|
|
@@ -347,7 +417,6 @@ def function_to_concept(
|
|
|
347
417
|
else:
|
|
348
418
|
derivation = Derivation.BASIC
|
|
349
419
|
granularity = Granularity.MULTI_ROW
|
|
350
|
-
# granularity = Concept.calculate_granularity(derivation, grain, parent)
|
|
351
420
|
|
|
352
421
|
if grain is not None:
|
|
353
422
|
r = Concept(
|
|
@@ -695,6 +764,14 @@ def arbitrary_to_concept(
|
|
|
695
764
|
elif isinstance(parent, Function):
|
|
696
765
|
if not name:
|
|
697
766
|
name = f"{VIRTUAL_CONCEPT_PREFIX}_func_{parent.operator.value}_{string_to_hash(str(parent))}"
|
|
767
|
+
if parent.operator == FunctionType.GROUP:
|
|
768
|
+
return group_function_to_concept(
|
|
769
|
+
parent,
|
|
770
|
+
name,
|
|
771
|
+
environment=environment,
|
|
772
|
+
namespace=namespace,
|
|
773
|
+
metadata=metadata,
|
|
774
|
+
)
|
|
698
775
|
return function_to_concept(
|
|
699
776
|
parent,
|
|
700
777
|
name,
|
trilogy/parsing/parse_engine.py
CHANGED
|
@@ -754,6 +754,11 @@ class ParseToObjects(Transformer):
|
|
|
754
754
|
components=set([self.environment.concepts[a].address for a in args[0]])
|
|
755
755
|
)
|
|
756
756
|
|
|
757
|
+
@v_args(meta=True)
|
|
758
|
+
def aggregate_by(self, meta: Meta, args):
|
|
759
|
+
args = [self.environment.concepts[a] for a in args]
|
|
760
|
+
return self.function_factory.create_function(args, FunctionType.GROUP, meta)
|
|
761
|
+
|
|
757
762
|
def whole_grain_clause(self, args) -> WholeGrainWrapper:
|
|
758
763
|
return WholeGrainWrapper(where=args[0])
|
|
759
764
|
|
|
@@ -1436,12 +1441,14 @@ class ParseToObjects(Transformer):
|
|
|
1436
1441
|
return args[0]
|
|
1437
1442
|
|
|
1438
1443
|
def window(self, args):
|
|
1444
|
+
|
|
1439
1445
|
return Window(count=args[1].value, window_order=args[0])
|
|
1440
1446
|
|
|
1441
1447
|
def WINDOW_TYPE(self, args):
|
|
1442
1448
|
return WindowType(args.strip())
|
|
1443
1449
|
|
|
1444
1450
|
def window_item_over(self, args):
|
|
1451
|
+
|
|
1445
1452
|
return WindowItemOver(contents=args[0])
|
|
1446
1453
|
|
|
1447
1454
|
def window_item_order(self, args):
|
trilogy/parsing/trilogy.lark
CHANGED
|
@@ -108,12 +108,12 @@
|
|
|
108
108
|
|
|
109
109
|
// rank/lag/lead
|
|
110
110
|
WINDOW_TYPE: ("row_number"i|"rank"i|"lag"i|"lead"i | "sum"i | "avg"i | "max"i | "min"i ) /[\s]+/
|
|
111
|
-
|
|
112
|
-
window_item: WINDOW_TYPE int_lit? expr window_item_over? window_item_order?
|
|
113
|
-
|
|
111
|
+
|
|
114
112
|
window_item_over: ("OVER"i over_list)
|
|
115
|
-
|
|
113
|
+
|
|
116
114
|
window_item_order: ("ORDER"i? "BY"i order_list)
|
|
115
|
+
|
|
116
|
+
window_item: WINDOW_TYPE int_lit? expr window_item_over? window_item_order?
|
|
117
117
|
|
|
118
118
|
select_hide_modifier: "--"
|
|
119
119
|
select_partial_modifier: "~"
|
|
@@ -185,7 +185,7 @@
|
|
|
185
185
|
attr_access: expr "." string_lit
|
|
186
186
|
|
|
187
187
|
|
|
188
|
-
expr:
|
|
188
|
+
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 | aggregate_by | custom_function
|
|
189
189
|
|
|
190
190
|
// functions
|
|
191
191
|
|
|
@@ -250,6 +250,10 @@
|
|
|
250
250
|
// special aggregate
|
|
251
251
|
_GROUP.1: "group("i
|
|
252
252
|
fgroup: _GROUP expr ")" aggregate_over?
|
|
253
|
+
|
|
254
|
+
//by:
|
|
255
|
+
aggregate_by: "group" IDENTIFIER "BY"i (IDENTIFIER ",")* IDENTIFIER
|
|
256
|
+
|
|
253
257
|
//aggregates
|
|
254
258
|
_COUNT.1: "count("i
|
|
255
259
|
count: _COUNT expr ")"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|