pytrilogy 0.0.3.46__py3-none-any.whl → 0.0.3.47__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.46.dist-info → pytrilogy-0.0.3.47.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.3.46.dist-info → pytrilogy-0.0.3.47.dist-info}/RECORD +18 -18
- {pytrilogy-0.0.3.46.dist-info → pytrilogy-0.0.3.47.dist-info}/WHEEL +1 -1
- trilogy/__init__.py +1 -1
- trilogy/core/environment_helpers.py +2 -27
- trilogy/core/models/author.py +4 -1
- trilogy/core/models/build.py +33 -29
- trilogy/core/processing/nodes/__init__.py +2 -0
- trilogy/core/query_processor.py +1 -2
- trilogy/core/statements/author.py +21 -9
- trilogy/dialect/base.py +1 -0
- trilogy/parsing/common.py +74 -16
- trilogy/parsing/exceptions.py +6 -0
- trilogy/parsing/parse_engine.py +33 -28
- trilogy/parsing/trilogy.lark +1 -4
- {pytrilogy-0.0.3.46.dist-info → pytrilogy-0.0.3.47.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.3.46.dist-info → pytrilogy-0.0.3.47.dist-info}/licenses/LICENSE.md +0 -0
- {pytrilogy-0.0.3.46.dist-info → pytrilogy-0.0.3.47.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.47.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
2
|
+
trilogy/__init__.py,sha256=SPFHWIHKOsEgt2qXg_bEpYrsQE1oX5pNjD1gj43FZW4,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
|
|
@@ -13,17 +13,17 @@ trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
13
13
|
trilogy/core/constants.py,sha256=7XaCpZn5mQmjTobbeBn56SzPWq9eMNDfzfsRU-fP0VE,171
|
|
14
14
|
trilogy/core/enums.py,sha256=JwbWyAHOC2xRTZe2SeEvlIGPvmC1KjcJ4uh1Po5USzQ,7380
|
|
15
15
|
trilogy/core/env_processor.py,sha256=pFsxnluKIusGKx1z7tTnfsd_xZcPy9pZDungkjkyvI0,3170
|
|
16
|
-
trilogy/core/environment_helpers.py,sha256=
|
|
16
|
+
trilogy/core/environment_helpers.py,sha256=VvPIiFemqaLLpIpLIqprfu63K7muZ1YzNg7UZIUph8w,8267
|
|
17
17
|
trilogy/core/ergonomics.py,sha256=e-7gE29vPLFdg0_A1smQ7eOrUwKl5VYdxRSTddHweRA,1631
|
|
18
18
|
trilogy/core/exceptions.py,sha256=JPYyBcit3T_pRtlHdtKSeVJkIyWUTozW2aaut25A2xI,673
|
|
19
19
|
trilogy/core/functions.py,sha256=4fEOGgXWDvgrJtCg_5m2Y9iWnHfLbvLQ82RkIMl_1K0,27722
|
|
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=FFZlTzF9DBDH7dvpTDgd5SxDkE4EkING-MVtUgqx9gQ,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=NhTKuk1eYAuYBbpvaFUxr-LntIoVarFQlNuNJwZmMmw,76990
|
|
26
|
+
trilogy/core/models/build.py,sha256=fFPGyHIPzQU8DNOxkGQGC3_sbZt-MHP0o5ftSA67LtU,61962
|
|
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
|
|
@@ -55,7 +55,7 @@ trilogy/core/processing/node_generators/unnest_node.py,sha256=cOEKnMRzXUW3bwmiOl
|
|
|
55
55
|
trilogy/core/processing/node_generators/window_node.py,sha256=RUHgpYovQObFod1xRIMWtDzMcxwlm4-1Fdrf_Cuw5W4,6346
|
|
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=xPFF7x3TFs1Z4IcfthCykZgrksb-UhN-pc_oIigfFSo,6014
|
|
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
|
|
@@ -65,12 +65,12 @@ trilogy/core/processing/nodes/union_node.py,sha256=fDFzLAUh5876X6_NM7nkhoMvHEdGJ
|
|
|
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=2q0yvP_0AbExdXnASlLG7OaDcM7sBaRco6YALnrQwzg,15255
|
|
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=kIwkNWWhMEIWDJ-6nr5bb2CJgbpKvJ9CxbhH4tYOFEc,41482
|
|
74
74
|
trilogy/dialect/bigquery.py,sha256=7LcgPLDkeNBk6YTfaE-RBBi7SjWFV-jjuvZM1VMIXqk,3350
|
|
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=CCCkoRkFG9nxBI6u0lWt81JZvtBvXbTdiblvexOaeSY,29250
|
|
91
91
|
trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
|
|
92
|
-
trilogy/parsing/exceptions.py,sha256=
|
|
92
|
+
trilogy/parsing/exceptions.py,sha256=Xwwsv2C9kSNv2q-HrrKC1f60JNHShXcCMzstTSEbiCw,154
|
|
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=m8t6De4q-PhqthMl7iIJsB7JdSQ-YYnBlLumTkLzw1Q,68652
|
|
95
95
|
trilogy/parsing/render.py,sha256=hI4y-xjXrEXvHslY2l2TQ8ic0zAOpN41ADH37J2_FZY,19047
|
|
96
|
-
trilogy/parsing/trilogy.lark,sha256=
|
|
96
|
+
trilogy/parsing/trilogy.lark,sha256=q15J3P71yA_4lsWjC1vb7eDTemkJGLPKYvf5Hn9IBIk,13584
|
|
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.47.dist-info/METADATA,sha256=pWnJM7LvUZHAV5x2DH41DNeZHCo3lqmuEwmSPeWCeJQ,9100
|
|
106
|
+
pytrilogy-0.0.3.47.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
|
|
107
|
+
pytrilogy-0.0.3.47.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
|
|
108
|
+
pytrilogy-0.0.3.47.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
109
|
+
pytrilogy-0.0.3.47.dist-info/RECORD,,
|
trilogy/__init__.py
CHANGED
|
@@ -6,23 +6,8 @@ from trilogy.core.models.core import DataType, StructType, arg_to_datatype
|
|
|
6
6
|
from trilogy.core.models.environment import Environment
|
|
7
7
|
from trilogy.parsing.common import Meta
|
|
8
8
|
|
|
9
|
-
FUNCTION_DESCRIPTION_MAPS = {
|
|
10
|
-
FunctionType.DATE: "The date part of a timestamp/date. Integer, 0-31 depending on month.",
|
|
11
|
-
FunctionType.MONTH: "The month part of a timestamp/date. Integer, 1-12.",
|
|
12
|
-
FunctionType.YEAR: "The year part of a timestamp/date. Integer.",
|
|
13
|
-
FunctionType.QUARTER: "The quarter part of a timestamp/date. Integer, 1-4.",
|
|
14
|
-
FunctionType.DAY_OF_WEEK: "The day of the week part of a timestamp/date. Integer, 0-6.",
|
|
15
|
-
FunctionType.HOUR: "The hour part of a timestamp. Integer, 0-23.",
|
|
16
|
-
FunctionType.MINUTE: "The minute part of a timestamp. Integer, 0-59.",
|
|
17
|
-
FunctionType.SECOND: "The second part of a timestamp. Integer, 0-59.",
|
|
18
|
-
}
|
|
19
|
-
|
|
20
9
|
|
|
21
10
|
def generate_date_concepts(concept: Concept, environment: Environment):
|
|
22
|
-
if concept.metadata and concept.metadata.description:
|
|
23
|
-
base_description = concept.metadata.description
|
|
24
|
-
else:
|
|
25
|
-
base_description = f"a {concept.address}"
|
|
26
11
|
if concept.metadata and concept.metadata.line_number:
|
|
27
12
|
base_line_number = concept.metadata.line_number
|
|
28
13
|
else:
|
|
@@ -67,7 +52,6 @@ def generate_date_concepts(concept: Concept, environment: Environment):
|
|
|
67
52
|
[concept.address],
|
|
68
53
|
),
|
|
69
54
|
metadata=Metadata(
|
|
70
|
-
description=f"Auto-derived from {base_description}. {FUNCTION_DESCRIPTION_MAPS.get(ftype, ftype.value)}",
|
|
71
55
|
line_number=base_line_number,
|
|
72
56
|
concept_source=ConceptSource.AUTO_DERIVED,
|
|
73
57
|
),
|
|
@@ -95,7 +79,7 @@ def generate_date_concepts(concept: Concept, environment: Environment):
|
|
|
95
79
|
[concept.address],
|
|
96
80
|
),
|
|
97
81
|
metadata=Metadata(
|
|
98
|
-
description=f"Auto-derived from {base_description}. The date truncated to the {grain.value}.",
|
|
82
|
+
# description=f"Auto-derived from {base_description}. The date truncated to the {grain.value}.",
|
|
99
83
|
line_number=base_line_number,
|
|
100
84
|
concept_source=ConceptSource.AUTO_DERIVED,
|
|
101
85
|
),
|
|
@@ -105,10 +89,6 @@ def generate_date_concepts(concept: Concept, environment: Environment):
|
|
|
105
89
|
|
|
106
90
|
|
|
107
91
|
def generate_datetime_concepts(concept: Concept, environment: Environment):
|
|
108
|
-
if concept.metadata and concept.metadata.description:
|
|
109
|
-
base_description = concept.metadata.description
|
|
110
|
-
else:
|
|
111
|
-
base_description = concept.address
|
|
112
92
|
if concept.metadata and concept.metadata.line_number:
|
|
113
93
|
base_line_number = concept.metadata.line_number
|
|
114
94
|
else:
|
|
@@ -146,7 +126,6 @@ def generate_datetime_concepts(concept: Concept, environment: Environment):
|
|
|
146
126
|
[concept.address],
|
|
147
127
|
),
|
|
148
128
|
metadata=Metadata(
|
|
149
|
-
description=f"Auto-derived from {base_description}. {FUNCTION_DESCRIPTION_MAPS.get(ftype, ftype.value)}",
|
|
150
129
|
line_number=base_line_number,
|
|
151
130
|
concept_source=ConceptSource.AUTO_DERIVED,
|
|
152
131
|
),
|
|
@@ -157,10 +136,6 @@ def generate_datetime_concepts(concept: Concept, environment: Environment):
|
|
|
157
136
|
|
|
158
137
|
|
|
159
138
|
def generate_key_concepts(concept: Concept, environment: Environment):
|
|
160
|
-
if concept.metadata and concept.metadata.description:
|
|
161
|
-
base_description = concept.metadata.description
|
|
162
|
-
else:
|
|
163
|
-
base_description = f"a {concept.datatype.value}"
|
|
164
139
|
if concept.metadata and concept.metadata.line_number:
|
|
165
140
|
base_line_number = concept.metadata.line_number
|
|
166
141
|
else:
|
|
@@ -186,7 +161,7 @@ def generate_key_concepts(concept: Concept, environment: Environment):
|
|
|
186
161
|
namespace=concept.namespace,
|
|
187
162
|
keys=set(),
|
|
188
163
|
metadata=Metadata(
|
|
189
|
-
description=f"Auto-derived integer. The {ftype.value} of {concept.address}, {base_description}",
|
|
164
|
+
# description=f"Auto-derived integer. The {ftype.value} of {concept.address}, {base_description}",
|
|
190
165
|
line_number=base_line_number,
|
|
191
166
|
concept_source=ConceptSource.AUTO_DERIVED,
|
|
192
167
|
),
|
trilogy/core/models/author.py
CHANGED
|
@@ -445,13 +445,16 @@ class Grain(Namespaced, BaseModel):
|
|
|
445
445
|
concepts: Iterable[Concept | ConceptRef | str],
|
|
446
446
|
environment: Environment | None = None,
|
|
447
447
|
where_clause: WhereClause | None = None,
|
|
448
|
+
local_concepts: dict[str, Concept] | None = None,
|
|
448
449
|
) -> Grain:
|
|
449
450
|
from trilogy.parsing.common import concepts_to_grain_concepts
|
|
450
451
|
|
|
451
452
|
x = Grain.model_construct(
|
|
452
453
|
components={
|
|
453
454
|
c.address
|
|
454
|
-
for c in concepts_to_grain_concepts(
|
|
455
|
+
for c in concepts_to_grain_concepts(
|
|
456
|
+
concepts, environment=environment, local_concepts=local_concepts
|
|
457
|
+
)
|
|
455
458
|
},
|
|
456
459
|
where_clause=where_clause,
|
|
457
460
|
)
|
trilogy/core/models/build.py
CHANGED
|
@@ -126,9 +126,14 @@ def concept_is_relevant(
|
|
|
126
126
|
|
|
127
127
|
return False
|
|
128
128
|
if concept.purpose in (Purpose.PROPERTY, Purpose.METRIC) and concept.keys:
|
|
129
|
-
if
|
|
130
|
-
|
|
129
|
+
if all([c in others for c in concept.keys]):
|
|
131
130
|
return False
|
|
131
|
+
if (
|
|
132
|
+
concept.purpose == Purpose.KEY
|
|
133
|
+
and concept.keys
|
|
134
|
+
and all([c in others for c in concept.keys])
|
|
135
|
+
):
|
|
136
|
+
return False
|
|
132
137
|
if concept.purpose in (Purpose.METRIC,):
|
|
133
138
|
if all([c in others for c in concept.grain.components]):
|
|
134
139
|
return False
|
|
@@ -1458,11 +1463,35 @@ class Factory:
|
|
|
1458
1463
|
{} if local_concepts is None else local_concepts
|
|
1459
1464
|
)
|
|
1460
1465
|
|
|
1466
|
+
def instantiate_concept(
|
|
1467
|
+
self,
|
|
1468
|
+
arg: (
|
|
1469
|
+
AggregateWrapper
|
|
1470
|
+
| FunctionCallWrapper
|
|
1471
|
+
| WindowItem
|
|
1472
|
+
| FilterItem
|
|
1473
|
+
| Function
|
|
1474
|
+
| ListWrapper[Any]
|
|
1475
|
+
| MapWrapper[Any, Any]
|
|
1476
|
+
| int
|
|
1477
|
+
| float
|
|
1478
|
+
| str
|
|
1479
|
+
),
|
|
1480
|
+
) -> tuple[Concept, BuildConcept]:
|
|
1481
|
+
from trilogy.parsing.common import arbitrary_to_concept
|
|
1482
|
+
|
|
1483
|
+
new = arbitrary_to_concept(
|
|
1484
|
+
arg,
|
|
1485
|
+
environment=self.environment,
|
|
1486
|
+
)
|
|
1487
|
+
built = self.build(new)
|
|
1488
|
+
self.local_concepts[new.address] = built
|
|
1489
|
+
return new, built
|
|
1490
|
+
|
|
1461
1491
|
@singledispatchmethod
|
|
1462
1492
|
def build(self, base):
|
|
1463
1493
|
raise NotImplementedError("Cannot build {}".format(type(base)))
|
|
1464
1494
|
|
|
1465
|
-
@build.register
|
|
1466
1495
|
@build.register
|
|
1467
1496
|
def _(
|
|
1468
1497
|
self,
|
|
@@ -1496,31 +1525,6 @@ class Factory:
|
|
|
1496
1525
|
):
|
|
1497
1526
|
return base
|
|
1498
1527
|
|
|
1499
|
-
def instantiate_concept(
|
|
1500
|
-
self,
|
|
1501
|
-
arg: (
|
|
1502
|
-
AggregateWrapper
|
|
1503
|
-
| FunctionCallWrapper
|
|
1504
|
-
| WindowItem
|
|
1505
|
-
| FilterItem
|
|
1506
|
-
| Function
|
|
1507
|
-
| ListWrapper[Any]
|
|
1508
|
-
| MapWrapper[Any, Any]
|
|
1509
|
-
| int
|
|
1510
|
-
| float
|
|
1511
|
-
| str
|
|
1512
|
-
),
|
|
1513
|
-
) -> tuple[Concept, BuildConcept]:
|
|
1514
|
-
from trilogy.parsing.common import arbitrary_to_concept
|
|
1515
|
-
|
|
1516
|
-
new = arbitrary_to_concept(
|
|
1517
|
-
arg,
|
|
1518
|
-
environment=self.environment,
|
|
1519
|
-
)
|
|
1520
|
-
built = self.build(new)
|
|
1521
|
-
self.local_concepts[new.address] = built
|
|
1522
|
-
return new, built
|
|
1523
|
-
|
|
1524
1528
|
@build.register
|
|
1525
1529
|
def _(self, base: None) -> None:
|
|
1526
1530
|
return base
|
|
@@ -1626,6 +1630,7 @@ class Factory:
|
|
|
1626
1630
|
derivation, final_grain, build_lineage
|
|
1627
1631
|
)
|
|
1628
1632
|
is_aggregate = Concept.calculate_is_aggregate(build_lineage)
|
|
1633
|
+
|
|
1629
1634
|
rval = BuildConcept.model_construct(
|
|
1630
1635
|
name=base.name,
|
|
1631
1636
|
datatype=base.datatype,
|
|
@@ -1646,7 +1651,6 @@ class Factory:
|
|
|
1646
1651
|
|
|
1647
1652
|
@build.register
|
|
1648
1653
|
def _(self, base: AggregateWrapper) -> BuildAggregateWrapper:
|
|
1649
|
-
|
|
1650
1654
|
if not base.by:
|
|
1651
1655
|
by = [
|
|
1652
1656
|
self.build(self.environment.concepts[c]) for c in self.grain.components
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from pydantic import BaseModel, ConfigDict, Field
|
|
2
2
|
|
|
3
3
|
from trilogy.core.exceptions import UnresolvableQueryException
|
|
4
|
+
from trilogy.core.models.author import Concept
|
|
4
5
|
from trilogy.core.models.build import BuildConcept, BuildWhereClause
|
|
5
6
|
from trilogy.core.models.build_environment import BuildEnvironment
|
|
6
7
|
from trilogy.core.models.environment import Environment
|
|
@@ -17,6 +18,7 @@ from .window_node import WindowNode
|
|
|
17
18
|
|
|
18
19
|
class History(BaseModel):
|
|
19
20
|
base_environment: Environment
|
|
21
|
+
local_base_concepts: dict[str, Concept] = Field(default_factory=dict)
|
|
20
22
|
history: dict[str, StrategyNode | None] = Field(default_factory=dict)
|
|
21
23
|
select_history: dict[str, StrategyNode | None] = Field(default_factory=dict)
|
|
22
24
|
started: dict[str, int] = Field(default_factory=dict)
|
trilogy/core/query_processor.py
CHANGED
|
@@ -373,10 +373,9 @@ def get_query_node(
|
|
|
373
373
|
) -> StrategyNode:
|
|
374
374
|
if not statement.output_components:
|
|
375
375
|
raise ValueError(f"Statement has no output components {statement}")
|
|
376
|
-
|
|
377
376
|
history = history or History(base_environment=environment)
|
|
378
377
|
build_statement: BuildSelectLineage | BuildMultiSelectLineage = Factory(
|
|
379
|
-
environment=environment
|
|
378
|
+
environment=environment,
|
|
380
379
|
).build(statement)
|
|
381
380
|
|
|
382
381
|
# build_statement = statement
|
|
@@ -7,6 +7,7 @@ from pydantic.functional_validators import PlainValidator
|
|
|
7
7
|
|
|
8
8
|
from trilogy.constants import CONFIG
|
|
9
9
|
from trilogy.core.enums import (
|
|
10
|
+
ConceptSource,
|
|
10
11
|
FunctionClass,
|
|
11
12
|
IOType,
|
|
12
13
|
Modifier,
|
|
@@ -134,7 +135,7 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
|
|
|
134
135
|
meta=meta or Metadata(),
|
|
135
136
|
)
|
|
136
137
|
|
|
137
|
-
output.grain = output.calculate_grain(environment)
|
|
138
|
+
output.grain = output.calculate_grain(environment, output.local_concepts)
|
|
138
139
|
|
|
139
140
|
for x in selection:
|
|
140
141
|
if x.is_undefined and environment.concepts.fail_on_missing:
|
|
@@ -144,12 +145,13 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
|
|
|
144
145
|
elif isinstance(x.content, ConceptTransform):
|
|
145
146
|
if isinstance(x.content.output, UndefinedConcept):
|
|
146
147
|
continue
|
|
147
|
-
if
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
148
|
+
if CONFIG.parsing.select_as_definition and not environment.frozen:
|
|
149
|
+
if x.concept.address not in environment.concepts:
|
|
150
|
+
environment.add_concept(x.content.output)
|
|
151
|
+
elif x.concept.address in environment.concepts:
|
|
152
|
+
version = environment.concepts[x.concept.address]
|
|
153
|
+
if version.metadata.concept_source == ConceptSource.SELECT:
|
|
154
|
+
environment.add_concept(x.content.output, force=True)
|
|
153
155
|
x.content.output = x.content.output.set_select_grain(
|
|
154
156
|
output.grain, environment
|
|
155
157
|
)
|
|
@@ -160,16 +162,26 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
|
|
|
160
162
|
output.local_concepts[x.content.address] = environment.concepts[
|
|
161
163
|
x.content.address
|
|
162
164
|
]
|
|
165
|
+
|
|
166
|
+
output.grain = output.calculate_grain(environment, output.local_concepts)
|
|
167
|
+
|
|
163
168
|
output.validate_syntax(environment)
|
|
164
169
|
return output
|
|
165
170
|
|
|
166
|
-
def calculate_grain(
|
|
171
|
+
def calculate_grain(
|
|
172
|
+
self,
|
|
173
|
+
environment: Environment | None = None,
|
|
174
|
+
local_concepts: dict[str, Concept] | None = None,
|
|
175
|
+
) -> Grain:
|
|
167
176
|
targets = []
|
|
168
177
|
for x in self.selection:
|
|
169
178
|
targets.append(x.concept)
|
|
170
179
|
|
|
171
180
|
result = Grain.from_concepts(
|
|
172
|
-
targets,
|
|
181
|
+
targets,
|
|
182
|
+
where_clause=self.where_clause,
|
|
183
|
+
environment=environment,
|
|
184
|
+
local_concepts=local_concepts,
|
|
173
185
|
)
|
|
174
186
|
return result
|
|
175
187
|
|
trilogy/dialect/base.py
CHANGED
|
@@ -199,6 +199,7 @@ FUNCTION_MAP = {
|
|
|
199
199
|
FunctionType.DATE_TRUNCATE: lambda x: f"date_trunc({x[0]},{x[1]})",
|
|
200
200
|
FunctionType.DATE_PART: lambda x: f"date_part({x[0]},{x[1]})",
|
|
201
201
|
FunctionType.DATE_ADD: lambda x: f"date_add({x[0]},{x[1]}, {x[2]})",
|
|
202
|
+
FunctionType.DATE_SUB: lambda x: f"date_sub({x[0]},{x[1]}, {x[2]})",
|
|
202
203
|
FunctionType.DATE_DIFF: lambda x: f"date_diff({x[0]},{x[1]}, {x[2]})",
|
|
203
204
|
FunctionType.DATE: lambda x: f"date({x[0]})",
|
|
204
205
|
FunctionType.DATETIME: lambda x: f"datetime({x[0]})",
|
trilogy/parsing/common.py
CHANGED
|
@@ -24,6 +24,7 @@ from trilogy.core.models.author import (
|
|
|
24
24
|
AlignClause,
|
|
25
25
|
AlignItem,
|
|
26
26
|
Concept,
|
|
27
|
+
ConceptArgs,
|
|
27
28
|
ConceptRef,
|
|
28
29
|
FilterItem,
|
|
29
30
|
Function,
|
|
@@ -191,16 +192,53 @@ def constant_to_concept(
|
|
|
191
192
|
)
|
|
192
193
|
|
|
193
194
|
|
|
195
|
+
def atom_is_relevant(
|
|
196
|
+
atom,
|
|
197
|
+
others: list[Concept | ConceptRef],
|
|
198
|
+
environment: Environment | None = None,
|
|
199
|
+
):
|
|
200
|
+
if isinstance(atom, (ConceptRef, Concept)):
|
|
201
|
+
# when we are looking at atoms, if there is a concept that is in others
|
|
202
|
+
# return directly
|
|
203
|
+
if atom.address in others:
|
|
204
|
+
return False
|
|
205
|
+
return concept_is_relevant(atom, others, environment)
|
|
206
|
+
|
|
207
|
+
if isinstance(atom, AggregateWrapper) and not atom.by:
|
|
208
|
+
return False
|
|
209
|
+
elif isinstance(atom, AggregateWrapper):
|
|
210
|
+
return any(atom_is_relevant(x, others, environment) for x in atom.by)
|
|
211
|
+
|
|
212
|
+
if isinstance(atom, Function):
|
|
213
|
+
relevant = False
|
|
214
|
+
print("atom args")
|
|
215
|
+
for arg in atom.arguments:
|
|
216
|
+
relevant = relevant or atom_is_relevant(arg, others, environment)
|
|
217
|
+
return relevant
|
|
218
|
+
elif isinstance(atom, FunctionCallWrapper):
|
|
219
|
+
return any(
|
|
220
|
+
[atom_is_relevant(atom.content, others, environment)]
|
|
221
|
+
+ [atom_is_relevant(x, others, environment) for x in atom.args]
|
|
222
|
+
)
|
|
223
|
+
elif isinstance(atom, ConceptArgs):
|
|
224
|
+
# use atom is relevant here to trigger the early exit behavior for concpets in set
|
|
225
|
+
return any(
|
|
226
|
+
[atom_is_relevant(x, others, environment) for x in atom.concept_arguments]
|
|
227
|
+
)
|
|
228
|
+
return False
|
|
229
|
+
|
|
230
|
+
|
|
194
231
|
def concept_is_relevant(
|
|
195
232
|
concept: Concept | ConceptRef,
|
|
196
233
|
others: list[Concept | ConceptRef],
|
|
197
234
|
environment: Environment | None = None,
|
|
198
235
|
) -> bool:
|
|
199
|
-
if isinstance(concept, UndefinedConcept):
|
|
200
236
|
|
|
237
|
+
if isinstance(concept, UndefinedConcept):
|
|
201
238
|
return False
|
|
202
239
|
if concept.datatype == DataType.UNKNOWN:
|
|
203
240
|
return False
|
|
241
|
+
|
|
204
242
|
if isinstance(concept, ConceptRef):
|
|
205
243
|
if environment:
|
|
206
244
|
concept = environment.concepts[concept.address]
|
|
@@ -208,41 +246,56 @@ def concept_is_relevant(
|
|
|
208
246
|
raise SyntaxError(
|
|
209
247
|
"Require environment to determine relevance of ConceptRef"
|
|
210
248
|
)
|
|
211
|
-
|
|
249
|
+
if concept.derivation == Derivation.CONSTANT:
|
|
250
|
+
return False
|
|
212
251
|
if concept.is_aggregate and not (
|
|
213
252
|
isinstance(concept.lineage, AggregateWrapper) and concept.lineage.by
|
|
214
253
|
):
|
|
215
254
|
|
|
216
255
|
return False
|
|
217
256
|
if concept.purpose in (Purpose.PROPERTY, Purpose.METRIC) and concept.keys:
|
|
218
|
-
if
|
|
219
|
-
|
|
257
|
+
if all([c in others for c in concept.keys]):
|
|
220
258
|
return False
|
|
259
|
+
if (
|
|
260
|
+
concept.purpose == Purpose.KEY
|
|
261
|
+
and concept.keys
|
|
262
|
+
and all([c in others for c in concept.keys])
|
|
263
|
+
):
|
|
264
|
+
return False
|
|
221
265
|
if concept.purpose in (Purpose.METRIC,):
|
|
222
266
|
if all([c in others for c in concept.grain.components]):
|
|
223
267
|
return False
|
|
224
|
-
if concept.derivation in (Derivation.BASIC,)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
268
|
+
if concept.derivation in (Derivation.BASIC,) and isinstance(
|
|
269
|
+
concept.lineage, Function
|
|
270
|
+
):
|
|
271
|
+
relevant = False
|
|
272
|
+
for arg in concept.lineage.arguments:
|
|
273
|
+
relevant = atom_is_relevant(arg, others, environment) or relevant
|
|
274
|
+
return relevant
|
|
229
275
|
if concept.granularity == Granularity.SINGLE_ROW:
|
|
230
276
|
return False
|
|
231
277
|
return True
|
|
232
278
|
|
|
233
279
|
|
|
234
280
|
def concepts_to_grain_concepts(
|
|
235
|
-
concepts: Iterable[Concept | ConceptRef | str],
|
|
281
|
+
concepts: Iterable[Concept | ConceptRef | str],
|
|
282
|
+
environment: Environment | None,
|
|
283
|
+
local_concepts: dict[str, Concept] | None = None,
|
|
236
284
|
) -> list[Concept]:
|
|
237
285
|
pconcepts: list[Concept] = []
|
|
238
286
|
for c in concepts:
|
|
239
|
-
|
|
240
287
|
if isinstance(c, Concept):
|
|
241
288
|
pconcepts.append(c)
|
|
242
289
|
elif isinstance(c, ConceptRef) and environment:
|
|
243
|
-
|
|
290
|
+
if local_concepts and c.address in local_concepts:
|
|
291
|
+
pconcepts.append(local_concepts[c.address])
|
|
292
|
+
else:
|
|
293
|
+
pconcepts.append(environment.concepts[c.address])
|
|
244
294
|
elif isinstance(c, str) and environment:
|
|
245
|
-
|
|
295
|
+
if local_concepts and c in local_concepts:
|
|
296
|
+
pconcepts.append(local_concepts[c])
|
|
297
|
+
else:
|
|
298
|
+
pconcepts.append(environment.concepts[c])
|
|
246
299
|
else:
|
|
247
300
|
raise ValueError(
|
|
248
301
|
f"Unable to resolve input {c} without environment provided to concepts_to_grain call"
|
|
@@ -250,6 +303,7 @@ def concepts_to_grain_concepts(
|
|
|
250
303
|
|
|
251
304
|
final: List[Concept] = []
|
|
252
305
|
for sub in pconcepts:
|
|
306
|
+
|
|
253
307
|
if not concept_is_relevant(sub, pconcepts, environment): # type: ignore
|
|
254
308
|
continue
|
|
255
309
|
final.append(sub)
|
|
@@ -366,7 +420,12 @@ def function_to_concept(
|
|
|
366
420
|
is_metric = False
|
|
367
421
|
ref_args, is_metric = get_relevant_parent_concepts(parent)
|
|
368
422
|
concrete_args = [environment.concepts[c.address] for c in ref_args]
|
|
369
|
-
pkeys += [
|
|
423
|
+
pkeys += [
|
|
424
|
+
x
|
|
425
|
+
for x in concrete_args
|
|
426
|
+
if not x.derivation == Derivation.CONSTANT
|
|
427
|
+
and not (x.derivation == Derivation.AGGREGATE and not x.grain.components)
|
|
428
|
+
]
|
|
370
429
|
grain: Grain | None = Grain()
|
|
371
430
|
for x in pkeys:
|
|
372
431
|
grain += x.grain
|
|
@@ -376,7 +435,7 @@ def function_to_concept(
|
|
|
376
435
|
modifiers = get_upstream_modifiers(pkeys, environment)
|
|
377
436
|
key_grain: list[str] = []
|
|
378
437
|
for x in pkeys:
|
|
379
|
-
# metrics will group to keys, so do
|
|
438
|
+
# metrics will group to keys, so do not do key traversal
|
|
380
439
|
if is_metric:
|
|
381
440
|
key_grain.append(x.address)
|
|
382
441
|
# otherwse, for row ops, assume keys are transitive
|
|
@@ -419,7 +478,6 @@ def function_to_concept(
|
|
|
419
478
|
else:
|
|
420
479
|
derivation = Derivation.BASIC
|
|
421
480
|
granularity = Granularity.MULTI_ROW
|
|
422
|
-
|
|
423
481
|
if grain is not None:
|
|
424
482
|
r = Concept(
|
|
425
483
|
name=name,
|
trilogy/parsing/exceptions.py
CHANGED
trilogy/parsing/parse_engine.py
CHANGED
|
@@ -117,7 +117,6 @@ from trilogy.core.statements.author import (
|
|
|
117
117
|
CopyStatement,
|
|
118
118
|
FunctionDeclaration,
|
|
119
119
|
ImportStatement,
|
|
120
|
-
KeyMergeStatement,
|
|
121
120
|
Limit,
|
|
122
121
|
MergeStatementV2,
|
|
123
122
|
MultiSelectStatement,
|
|
@@ -136,7 +135,7 @@ from trilogy.parsing.common import (
|
|
|
136
135
|
process_function_args,
|
|
137
136
|
rowset_to_concepts,
|
|
138
137
|
)
|
|
139
|
-
from trilogy.parsing.exceptions import ParseError
|
|
138
|
+
from trilogy.parsing.exceptions import NameShadowError, ParseError
|
|
140
139
|
|
|
141
140
|
perf_logger = getLogger("trilogy.parse.performance")
|
|
142
141
|
|
|
@@ -814,6 +813,24 @@ class ParseToObjects(Transformer):
|
|
|
814
813
|
)
|
|
815
814
|
if self.parse_pass == ParsePass.VALIDATION:
|
|
816
815
|
self.environment.add_datasource(datasource, meta=meta)
|
|
816
|
+
# if we have any foreign keys on the datasource, we can
|
|
817
|
+
# at this point optimize them to properties if they do not have other usage.
|
|
818
|
+
for column in columns:
|
|
819
|
+
# skip partial for now
|
|
820
|
+
if not grain:
|
|
821
|
+
continue
|
|
822
|
+
if column.concept.address in grain.components:
|
|
823
|
+
continue
|
|
824
|
+
target_c = self.environment.concepts[column.concept.address]
|
|
825
|
+
if target_c.purpose != Purpose.KEY:
|
|
826
|
+
continue
|
|
827
|
+
|
|
828
|
+
key_inputs = grain.components
|
|
829
|
+
keys = [self.environment.concepts[grain] for grain in key_inputs]
|
|
830
|
+
# target_c.purpose = Purpose.PROPERTY
|
|
831
|
+
target_c.keys = set([x.address for x in keys])
|
|
832
|
+
# target_c.grain = Grain(components={x.address for x in keys})
|
|
833
|
+
|
|
817
834
|
return datasource
|
|
818
835
|
|
|
819
836
|
@v_args(meta=True)
|
|
@@ -903,29 +920,6 @@ class ParseToObjects(Transformer):
|
|
|
903
920
|
def over_list(self, args):
|
|
904
921
|
return [x for x in args]
|
|
905
922
|
|
|
906
|
-
@v_args(meta=True)
|
|
907
|
-
def key_merge_statement(self, meta: Meta, args) -> KeyMergeStatement | None:
|
|
908
|
-
key_inputs = args[:-1]
|
|
909
|
-
target = args[-1]
|
|
910
|
-
keys = [self.environment.concepts[grain] for grain in key_inputs]
|
|
911
|
-
target_c = self.environment.concepts[target]
|
|
912
|
-
new = KeyMergeStatement(
|
|
913
|
-
keys=set([x.address for x in keys]),
|
|
914
|
-
target=target_c.reference,
|
|
915
|
-
)
|
|
916
|
-
internal = Concept(
|
|
917
|
-
name="_" + target_c.address.replace(".", "_"),
|
|
918
|
-
namespace=self.environment.namespace,
|
|
919
|
-
purpose=Purpose.PROPERTY,
|
|
920
|
-
keys=set([x.address for x in keys]),
|
|
921
|
-
datatype=target_c.datatype,
|
|
922
|
-
grain=Grain(components={x.address for x in keys}),
|
|
923
|
-
)
|
|
924
|
-
self.environment.add_concept(internal)
|
|
925
|
-
# always a full merge
|
|
926
|
-
self.environment.merge_concept(target_c, internal, [])
|
|
927
|
-
return new
|
|
928
|
-
|
|
929
923
|
@v_args(meta=True)
|
|
930
924
|
def merge_statement(self, meta: Meta, args) -> MergeStatementV2 | None:
|
|
931
925
|
modifiers = []
|
|
@@ -1253,9 +1247,18 @@ class ParseToObjects(Transformer):
|
|
|
1253
1247
|
):
|
|
1254
1248
|
intersection = base.locally_derived.intersection(pre_keys)
|
|
1255
1249
|
if intersection:
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1250
|
+
for x in intersection:
|
|
1251
|
+
if (
|
|
1252
|
+
base.local_concepts[x].derivation
|
|
1253
|
+
== self.environment.concepts[x].derivation
|
|
1254
|
+
):
|
|
1255
|
+
raise NameShadowError(
|
|
1256
|
+
f"Select statement {base} derives concept {x} with identical derivation as named concept. Use the named concept directly."
|
|
1257
|
+
)
|
|
1258
|
+
else:
|
|
1259
|
+
raise NameShadowError(
|
|
1260
|
+
f"Select statement {base} creates new derived concepts {list(intersection)} with identical name(s) to existing concept(s). If these are identical, reference the concept directly. Otherwise alias your column as a new name."
|
|
1261
|
+
)
|
|
1259
1262
|
return base
|
|
1260
1263
|
|
|
1261
1264
|
@v_args(meta=True)
|
|
@@ -1854,6 +1857,8 @@ class ParseToObjects(Transformer):
|
|
|
1854
1857
|
|
|
1855
1858
|
@v_args(meta=True)
|
|
1856
1859
|
def fround(self, meta, args) -> Function:
|
|
1860
|
+
if len(args) == 1:
|
|
1861
|
+
args.append(0)
|
|
1857
1862
|
return self.function_factory.create_function(args, FunctionType.ROUND, meta)
|
|
1858
1863
|
|
|
1859
1864
|
@v_args(meta=True)
|
trilogy/parsing/trilogy.lark
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
| rowset_derivation_statement
|
|
11
11
|
| import_statement
|
|
12
12
|
| copy_statement
|
|
13
|
-
| key_merge_statement
|
|
14
13
|
| merge_statement
|
|
15
14
|
| rawsql_statement
|
|
16
15
|
|
|
@@ -77,8 +76,6 @@
|
|
|
77
76
|
|
|
78
77
|
align_clause: align_item ("AND"i align_item)* "AND"i?
|
|
79
78
|
|
|
80
|
-
key_merge_statement: "merge"i "property"i "<" IDENTIFIER ("," IDENTIFIER )* ","? ">" "from"i IDENTIFIER
|
|
81
|
-
|
|
82
79
|
merge_statement: "merge"i WILDCARD_IDENTIFIER "into"i SHORTHAND_MODIFIER? WILDCARD_IDENTIFIER
|
|
83
80
|
|
|
84
81
|
// raw sql statement
|
|
@@ -223,7 +220,7 @@
|
|
|
223
220
|
fdiv: ( "divide"i "(" expr "," expr ")")
|
|
224
221
|
fmod: ( "mod"i "(" expr "," (int_lit | concept_lit ) ")")
|
|
225
222
|
_ROUND.1: "round"i "("
|
|
226
|
-
fround: _ROUND expr "," expr ")"
|
|
223
|
+
fround: _ROUND expr ("," expr)? ")"
|
|
227
224
|
fabs: "abs"i "(" expr ")"
|
|
228
225
|
_SQRT.1: "sqrt("
|
|
229
226
|
fsqrt: _SQRT expr ")"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|