pytrilogy 0.0.3.46__py3-none-any.whl → 0.0.3.48__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.48.dist-info}/METADATA +12 -15
- {pytrilogy-0.0.3.46.dist-info → pytrilogy-0.0.3.48.dist-info}/RECORD +24 -25
- {pytrilogy-0.0.3.46.dist-info → pytrilogy-0.0.3.48.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 +60 -34
- trilogy/core/models/execute.py +2 -1
- trilogy/core/optimization.py +0 -3
- trilogy/core/optimizations/__init__.py +0 -2
- trilogy/core/processing/node_generators/group_node.py +4 -1
- trilogy/core/processing/nodes/__init__.py +2 -0
- trilogy/core/query_processor.py +28 -14
- trilogy/core/statements/author.py +21 -9
- trilogy/dialect/base.py +22 -4
- trilogy/dialect/common.py +25 -14
- trilogy/executor.py +3 -0
- trilogy/parsing/common.py +73 -16
- trilogy/parsing/exceptions.py +6 -0
- trilogy/parsing/parse_engine.py +34 -29
- trilogy/parsing/trilogy.lark +1 -4
- trilogy/core/optimizations/inline_constant.py +0 -35
- {pytrilogy-0.0.3.46.dist-info → pytrilogy-0.0.3.48.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.3.46.dist-info → pytrilogy-0.0.3.48.dist-info}/licenses/LICENSE.md +0 -0
- {pytrilogy-0.0.3.46.dist-info → pytrilogy-0.0.3.48.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytrilogy
|
|
3
|
-
Version: 0.0.3.
|
|
3
|
+
Version: 0.0.3.48
|
|
4
4
|
Summary: Declarative, typed query language that compiles to SQL.
|
|
5
5
|
Home-page:
|
|
6
6
|
Author:
|
|
@@ -53,7 +53,7 @@ Installation: `pip install pytrilogy`
|
|
|
53
53
|
|
|
54
54
|
You can read more about the project [here](https://trilogydata.dev/) and try out an interactive demo [here](https://trilogydata.dev/demo/).
|
|
55
55
|
|
|
56
|
-
Trilogy:
|
|
56
|
+
Trilogy looks like SQL:
|
|
57
57
|
```sql
|
|
58
58
|
WHERE
|
|
59
59
|
name like '%lvis%'
|
|
@@ -65,7 +65,7 @@ ORDER BY
|
|
|
65
65
|
LIMIT 10;
|
|
66
66
|
```
|
|
67
67
|
## Goals
|
|
68
|
-
|
|
68
|
+
And aims to:
|
|
69
69
|
|
|
70
70
|
Preserve:
|
|
71
71
|
- Correctness
|
|
@@ -85,6 +85,7 @@ Maintain:
|
|
|
85
85
|
Save the following code in a file named `hello.preql`
|
|
86
86
|
|
|
87
87
|
```python
|
|
88
|
+
# semantic model is abstract from data
|
|
88
89
|
key sentence_id int;
|
|
89
90
|
property sentence_id.word_one string; # comments after a definition
|
|
90
91
|
property sentence_id.word_two string; # are syntactic sugar for adding
|
|
@@ -92,7 +93,8 @@ property sentence_id.word_three string; # a description to it
|
|
|
92
93
|
|
|
93
94
|
# comments in other places are just comments
|
|
94
95
|
|
|
95
|
-
# define our
|
|
96
|
+
# define our datasource to bind the model to data
|
|
97
|
+
# testing using query fixtures is a common pattern
|
|
96
98
|
datasource word_one(
|
|
97
99
|
sentence: sentence_id,
|
|
98
100
|
word:word_one
|
|
@@ -126,25 +128,20 @@ union all
|
|
|
126
128
|
select 2 as sentence, '!'
|
|
127
129
|
''';
|
|
128
130
|
|
|
131
|
+
def concat_with_space(x,y) -> x || ' ' || y;
|
|
132
|
+
|
|
129
133
|
# an actual select statement
|
|
130
134
|
# joins are automatically resolved between the 3 sources
|
|
131
135
|
with sentences as
|
|
132
|
-
select sentence_id, word_one
|
|
136
|
+
select sentence_id, @concat_with_space(word_one, word_two) || word_three as text;
|
|
133
137
|
|
|
134
|
-
SELECT
|
|
135
|
-
--sentences.sentence_id,
|
|
136
|
-
sentences.text
|
|
137
138
|
WHERE
|
|
138
|
-
sentences.sentence_id
|
|
139
|
-
;
|
|
140
|
-
|
|
139
|
+
sentences.sentence_id in (1,2)
|
|
141
140
|
SELECT
|
|
142
|
-
--sentences.sentence_id,
|
|
143
141
|
sentences.text
|
|
144
|
-
WHERE
|
|
145
|
-
sentences.sentence_id = 2
|
|
146
142
|
;
|
|
147
|
-
|
|
143
|
+
|
|
144
|
+
|
|
148
145
|
|
|
149
146
|
```
|
|
150
147
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
pytrilogy-0.0.3.
|
|
2
|
-
trilogy/__init__.py,sha256=
|
|
1
|
+
pytrilogy-0.0.3.48.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
2
|
+
trilogy/__init__.py,sha256=X7jtGsMd3bHz73UVPQxZzoipqeTD3gI4UEQZtAq3OUs,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
|
|
6
|
-
trilogy/executor.py,sha256=
|
|
6
|
+
trilogy/executor.py,sha256=GwNhP9UW4565dxnpHbw-VWNE2lX8uroQJQtSpC_j2pI,16298
|
|
7
7
|
trilogy/parser.py,sha256=o4cfk3j3yhUFoiDKq9ZX_GjBF3dKhDjXEwb63rcBkBM,293
|
|
8
8
|
trilogy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
9
|
trilogy/render.py,sha256=qQWwduymauOlB517UtM-VGbVe8Cswa4UJub5aGbSO6c,1512
|
|
@@ -13,25 +13,24 @@ 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
|
-
trilogy/core/optimization.py,sha256=
|
|
23
|
-
trilogy/core/query_processor.py,sha256=
|
|
22
|
+
trilogy/core/optimization.py,sha256=O7ag0IVQlJyWdAXBi_hHeU3Df5DRyd75Vlz6pks2J10,8197
|
|
23
|
+
trilogy/core/query_processor.py,sha256=NNzYPKN5HzivQFXugSbJC_MaupkwOYii7A_vnXuBIK4,20063
|
|
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=MPiHgyfOumZ8zF3iB61pzrAeDAlGV2F9R0Dw7mTTyqQ,62708
|
|
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
|
|
30
30
|
trilogy/core/models/environment.py,sha256=AVSrvjNcNX535GhCPtYhCRY2Lp_Hj0tdY3VVt_kZb9Q,27260
|
|
31
|
-
trilogy/core/models/execute.py,sha256=
|
|
32
|
-
trilogy/core/optimizations/__init__.py,sha256=
|
|
31
|
+
trilogy/core/models/execute.py,sha256=m_GodtQkhuPo5kyBNlfC9c_jgprV7M64kE6x_12_ExQ,34616
|
|
32
|
+
trilogy/core/optimizations/__init__.py,sha256=YH2-mGXZnVDnBcWVi8vTbrdw7Qs5TivG4h38rH3js_I,290
|
|
33
33
|
trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnbnb6vdVIztUfFQZsGnM,513
|
|
34
|
-
trilogy/core/optimizations/inline_constant.py,sha256=lvNTIXaLNkw3HseJyXyDNk5R52doLU9sIg3pmU2_S08,1332
|
|
35
34
|
trilogy/core/optimizations/inline_datasource.py,sha256=AHuTGh2x0GQ8usOe0NiFncfTFQ_KogdgDl4uucmhIbI,4241
|
|
36
35
|
trilogy/core/optimizations/predicate_pushdown.py,sha256=g4AYE8Aw_iMlAh68TjNXGP754NTurrDduFECkUjoBnc,9399
|
|
37
36
|
trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -42,7 +41,7 @@ trilogy/core/processing/node_generators/__init__.py,sha256=o8rOFHPSo-s_59hREwXMW
|
|
|
42
41
|
trilogy/core/processing/node_generators/basic_node.py,sha256=UVsXMn6jTjm_ofVFt218jAS11s4RV4zD781vP4im-GI,3371
|
|
43
42
|
trilogy/core/processing/node_generators/common.py,sha256=nVeH_AdO58ygtNSO0wNgMR7_h2D0dFSGM_rh1fJd4Yc,9468
|
|
44
43
|
trilogy/core/processing/node_generators/filter_node.py,sha256=JymSKzA-9oQAZ3ZtJRK9c3w5FXs8MjJBGWU9TYUqx4E,9099
|
|
45
|
-
trilogy/core/processing/node_generators/group_node.py,sha256=
|
|
44
|
+
trilogy/core/processing/node_generators/group_node.py,sha256=ISv2lLnr5m5nMpiXYJbgBqfUPQqeypjCAcaool9Kvnk,6109
|
|
46
45
|
trilogy/core/processing/node_generators/group_to_node.py,sha256=jKcNCDOY6fNblrdZwaRU0sbUSr9H0moQbAxrGgX6iGA,3832
|
|
47
46
|
trilogy/core/processing/node_generators/multiselect_node.py,sha256=GWV5yLmKTe1yyPhN60RG1Rnrn4ktfn9lYYXi_FVU4UI,7061
|
|
48
47
|
trilogy/core/processing/node_generators/node_merge_node.py,sha256=sv55oynfqgpHEpo1OEtVDri-5fywzPhDlR85qaWikvY,16195
|
|
@@ -55,7 +54,7 @@ trilogy/core/processing/node_generators/unnest_node.py,sha256=cOEKnMRzXUW3bwmiOl
|
|
|
55
54
|
trilogy/core/processing/node_generators/window_node.py,sha256=RUHgpYovQObFod1xRIMWtDzMcxwlm4-1Fdrf_Cuw5W4,6346
|
|
56
55
|
trilogy/core/processing/node_generators/select_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
57
56
|
trilogy/core/processing/node_generators/select_helpers/datasource_injection.py,sha256=GMW07bb6hXurhF0hZLYoMAKSIS65tat5hwBjvqqPeSA,6516
|
|
58
|
-
trilogy/core/processing/nodes/__init__.py,sha256=
|
|
57
|
+
trilogy/core/processing/nodes/__init__.py,sha256=xPFF7x3TFs1Z4IcfthCykZgrksb-UhN-pc_oIigfFSo,6014
|
|
59
58
|
trilogy/core/processing/nodes/base_node.py,sha256=FHrY8GsTKPuMJklOjILbhGqCt5s1nmlj62Z-molARDA,16835
|
|
60
59
|
trilogy/core/processing/nodes/filter_node.py,sha256=5VtRfKbCORx0dV-vQfgy3gOEkmmscL9f31ExvlODwvY,2461
|
|
61
60
|
trilogy/core/processing/nodes/group_node.py,sha256=MUvcOg9U5J6TnWBel8eht9PdI9BfAKjUxmfjP_ZXx9o,10484
|
|
@@ -65,14 +64,14 @@ trilogy/core/processing/nodes/union_node.py,sha256=fDFzLAUh5876X6_NM7nkhoMvHEdGJ
|
|
|
65
64
|
trilogy/core/processing/nodes/unnest_node.py,sha256=oLKMMNMx6PLDPlt2V5neFMFrFWxET8r6XZElAhSNkO0,2181
|
|
66
65
|
trilogy/core/processing/nodes/window_node.py,sha256=JXJ0iVRlSEM2IBr1TANym2RaUf_p5E_l2sNykRzXWDo,1710
|
|
67
66
|
trilogy/core/statements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
68
|
-
trilogy/core/statements/author.py,sha256=
|
|
67
|
+
trilogy/core/statements/author.py,sha256=2q0yvP_0AbExdXnASlLG7OaDcM7sBaRco6YALnrQwzg,15255
|
|
69
68
|
trilogy/core/statements/build.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
70
69
|
trilogy/core/statements/common.py,sha256=KxEmz2ySySyZ6CTPzn0fJl5NX2KOk1RPyuUSwWhnK1g,759
|
|
71
70
|
trilogy/core/statements/execute.py,sha256=cSlvpHFOqpiZ89pPZ5GDp9Hu6j6uj-5_h21FWm_L-KM,1248
|
|
72
71
|
trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
73
|
-
trilogy/dialect/base.py,sha256=
|
|
72
|
+
trilogy/dialect/base.py,sha256=RkfNoNSo46p-WCafAWC5tXqJ_FMZEXANLyZSqX7_Pxw,42082
|
|
74
73
|
trilogy/dialect/bigquery.py,sha256=7LcgPLDkeNBk6YTfaE-RBBi7SjWFV-jjuvZM1VMIXqk,3350
|
|
75
|
-
trilogy/dialect/common.py,sha256=
|
|
74
|
+
trilogy/dialect/common.py,sha256=JQ8ONloalaWEXsTTWUhZcYyzMRaZ9HdUw7cN6QWtY5c,5295
|
|
76
75
|
trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
|
|
77
76
|
trilogy/dialect/dataframe.py,sha256=RUbNgReEa9g3pL6H7fP9lPTrAij5pkqedpZ99D8_5AE,1522
|
|
78
77
|
trilogy/dialect/duckdb.py,sha256=XTBK4RhE1_wF2_IA_7c2W5ih0uxZx0wZ1mfJ3YFIuso,3768
|
|
@@ -87,13 +86,13 @@ trilogy/hooks/graph_hook.py,sha256=c-vC-IXoJ_jDmKQjxQyIxyXPOuUcLIURB573gCsAfzQ,2
|
|
|
87
86
|
trilogy/hooks/query_debugger.py,sha256=1npRjww94sPV5RRBBlLqMJRaFkH9vhEY6o828MeoEcw,5583
|
|
88
87
|
trilogy/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
89
88
|
trilogy/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
90
|
-
trilogy/parsing/common.py,sha256=
|
|
89
|
+
trilogy/parsing/common.py,sha256=u7V8uc2mdBtszVujk-hzllfDAqM3j5pKd8B9UEj-uNc,29223
|
|
91
90
|
trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
|
|
92
|
-
trilogy/parsing/exceptions.py,sha256=
|
|
91
|
+
trilogy/parsing/exceptions.py,sha256=Xwwsv2C9kSNv2q-HrrKC1f60JNHShXcCMzstTSEbiCw,154
|
|
93
92
|
trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
94
|
-
trilogy/parsing/parse_engine.py,sha256=
|
|
93
|
+
trilogy/parsing/parse_engine.py,sha256=K3TwjCiiZtG3UrICF9Alik56_KPusVNWfqE-oUaKfho,68664
|
|
95
94
|
trilogy/parsing/render.py,sha256=hI4y-xjXrEXvHslY2l2TQ8ic0zAOpN41ADH37J2_FZY,19047
|
|
96
|
-
trilogy/parsing/trilogy.lark,sha256=
|
|
95
|
+
trilogy/parsing/trilogy.lark,sha256=q15J3P71yA_4lsWjC1vb7eDTemkJGLPKYvf5Hn9IBIk,13584
|
|
97
96
|
trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
98
97
|
trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
|
|
99
98
|
trilogy/std/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -102,8 +101,8 @@ trilogy/std/display.preql,sha256=2BbhvqR4rcltyAbOXAUo7SZ_yGFYZgFnurglHMbjW2g,40
|
|
|
102
101
|
trilogy/std/geography.preql,sha256=-fqAGnBL6tR-UtT8DbSek3iMFg66ECR_B_41pODxv-k,504
|
|
103
102
|
trilogy/std/money.preql,sha256=ZHW-csTX-kYbOLmKSO-TcGGgQ-_DMrUXy0BjfuJSFxM,80
|
|
104
103
|
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.
|
|
104
|
+
pytrilogy-0.0.3.48.dist-info/METADATA,sha256=xuGEKV1ZdJtS-uES50q5bj5x-_60E4Pb6VL5G_SKzNQ,9095
|
|
105
|
+
pytrilogy-0.0.3.48.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
|
|
106
|
+
pytrilogy-0.0.3.48.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
|
|
107
|
+
pytrilogy-0.0.3.48.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
108
|
+
pytrilogy-0.0.3.48.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
|
|
@@ -242,6 +247,17 @@ def get_concept_arguments(expr) -> List["BuildConcept"]:
|
|
|
242
247
|
return output
|
|
243
248
|
|
|
244
249
|
|
|
250
|
+
class BuildParamaterizedConceptReference(BaseModel):
|
|
251
|
+
concept: BuildConcept
|
|
252
|
+
|
|
253
|
+
def __str__(self):
|
|
254
|
+
return f":{self.concept.address}"
|
|
255
|
+
|
|
256
|
+
@property
|
|
257
|
+
def safe_address(self) -> str:
|
|
258
|
+
return self.concept.safe_address
|
|
259
|
+
|
|
260
|
+
|
|
245
261
|
class BuildGrain(BaseModel):
|
|
246
262
|
components: set[str] = Field(default_factory=set)
|
|
247
263
|
where_clause: Optional[BuildWhereClause] = None
|
|
@@ -1458,11 +1474,35 @@ class Factory:
|
|
|
1458
1474
|
{} if local_concepts is None else local_concepts
|
|
1459
1475
|
)
|
|
1460
1476
|
|
|
1477
|
+
def instantiate_concept(
|
|
1478
|
+
self,
|
|
1479
|
+
arg: (
|
|
1480
|
+
AggregateWrapper
|
|
1481
|
+
| FunctionCallWrapper
|
|
1482
|
+
| WindowItem
|
|
1483
|
+
| FilterItem
|
|
1484
|
+
| Function
|
|
1485
|
+
| ListWrapper[Any]
|
|
1486
|
+
| MapWrapper[Any, Any]
|
|
1487
|
+
| int
|
|
1488
|
+
| float
|
|
1489
|
+
| str
|
|
1490
|
+
),
|
|
1491
|
+
) -> tuple[Concept, BuildConcept]:
|
|
1492
|
+
from trilogy.parsing.common import arbitrary_to_concept
|
|
1493
|
+
|
|
1494
|
+
new = arbitrary_to_concept(
|
|
1495
|
+
arg,
|
|
1496
|
+
environment=self.environment,
|
|
1497
|
+
)
|
|
1498
|
+
built = self.build(new)
|
|
1499
|
+
self.local_concepts[new.address] = built
|
|
1500
|
+
return new, built
|
|
1501
|
+
|
|
1461
1502
|
@singledispatchmethod
|
|
1462
1503
|
def build(self, base):
|
|
1463
1504
|
raise NotImplementedError("Cannot build {}".format(type(base)))
|
|
1464
1505
|
|
|
1465
|
-
@build.register
|
|
1466
1506
|
@build.register
|
|
1467
1507
|
def _(
|
|
1468
1508
|
self,
|
|
@@ -1496,31 +1536,6 @@ class Factory:
|
|
|
1496
1536
|
):
|
|
1497
1537
|
return base
|
|
1498
1538
|
|
|
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
1539
|
@build.register
|
|
1525
1540
|
def _(self, base: None) -> None:
|
|
1526
1541
|
return base
|
|
@@ -1574,7 +1589,7 @@ class Factory:
|
|
|
1574
1589
|
|
|
1575
1590
|
new = BuildFunction.model_construct(
|
|
1576
1591
|
operator=base.operator,
|
|
1577
|
-
arguments=[self.build(c) for c in raw_args],
|
|
1592
|
+
arguments=[self.handle_constant(self.build(c)) for c in raw_args],
|
|
1578
1593
|
output_datatype=base.output_datatype,
|
|
1579
1594
|
output_purpose=base.output_purpose,
|
|
1580
1595
|
valid_inputs=base.valid_inputs,
|
|
@@ -1611,6 +1626,8 @@ class Factory:
|
|
|
1611
1626
|
|
|
1612
1627
|
@build.register
|
|
1613
1628
|
def _(self, base: Concept) -> BuildConcept:
|
|
1629
|
+
|
|
1630
|
+
# TODO: if we are using parameters, wrap it in a new model and use that in rendering
|
|
1614
1631
|
if base.address in self.local_concepts:
|
|
1615
1632
|
return self.local_concepts[base.address]
|
|
1616
1633
|
new_lineage, final_grain, _ = base.get_select_grain_and_keys(
|
|
@@ -1626,6 +1643,7 @@ class Factory:
|
|
|
1626
1643
|
derivation, final_grain, build_lineage
|
|
1627
1644
|
)
|
|
1628
1645
|
is_aggregate = Concept.calculate_is_aggregate(build_lineage)
|
|
1646
|
+
|
|
1629
1647
|
rval = BuildConcept.model_construct(
|
|
1630
1648
|
name=base.name,
|
|
1631
1649
|
datatype=base.datatype,
|
|
@@ -1646,7 +1664,6 @@ class Factory:
|
|
|
1646
1664
|
|
|
1647
1665
|
@build.register
|
|
1648
1666
|
def _(self, base: AggregateWrapper) -> BuildAggregateWrapper:
|
|
1649
|
-
|
|
1650
1667
|
if not base.by:
|
|
1651
1668
|
by = [
|
|
1652
1669
|
self.build(self.environment.concepts[c]) for c in self.grain.components
|
|
@@ -1734,8 +1751,8 @@ class Factory:
|
|
|
1734
1751
|
@build.register
|
|
1735
1752
|
def _(self, base: Conditional) -> BuildConditional:
|
|
1736
1753
|
return BuildConditional.model_construct(
|
|
1737
|
-
left=(self.build(base.left)),
|
|
1738
|
-
right=(self.build(base.right)),
|
|
1754
|
+
left=self.handle_constant(self.build(base.left)),
|
|
1755
|
+
right=self.handle_constant(self.build(base.right)),
|
|
1739
1756
|
operator=base.operator,
|
|
1740
1757
|
)
|
|
1741
1758
|
|
|
@@ -1763,8 +1780,8 @@ class Factory:
|
|
|
1763
1780
|
right_c, _ = self.instantiate_concept(right)
|
|
1764
1781
|
right = right_c # type: ignore
|
|
1765
1782
|
return BuildComparison.model_construct(
|
|
1766
|
-
left=(self.build(left)),
|
|
1767
|
-
right=(self.build(right)),
|
|
1783
|
+
left=self.handle_constant(self.build(left)),
|
|
1784
|
+
right=self.handle_constant(self.build(right)),
|
|
1768
1785
|
operator=base.operator,
|
|
1769
1786
|
)
|
|
1770
1787
|
|
|
@@ -2011,3 +2028,12 @@ class Factory:
|
|
|
2011
2028
|
factory.build(base.non_partial_for) if base.non_partial_for else None
|
|
2012
2029
|
),
|
|
2013
2030
|
)
|
|
2031
|
+
|
|
2032
|
+
def handle_constant(self, base):
|
|
2033
|
+
if (
|
|
2034
|
+
isinstance(base, BuildConcept)
|
|
2035
|
+
and isinstance(base.lineage, BuildFunction)
|
|
2036
|
+
and base.lineage.operator == FunctionType.CONSTANT
|
|
2037
|
+
):
|
|
2038
|
+
return BuildParamaterizedConceptReference(concept=base)
|
|
2039
|
+
return base
|
trilogy/core/models/execute.py
CHANGED
|
@@ -24,6 +24,7 @@ from trilogy.core.models.build import (
|
|
|
24
24
|
BuildFunction,
|
|
25
25
|
BuildGrain,
|
|
26
26
|
BuildOrderBy,
|
|
27
|
+
BuildParamaterizedConceptReference,
|
|
27
28
|
BuildParenthetical,
|
|
28
29
|
BuildRowsetItem,
|
|
29
30
|
LooseBuildConceptList,
|
|
@@ -447,7 +448,7 @@ class CTEConceptPair(ConceptPair):
|
|
|
447
448
|
|
|
448
449
|
|
|
449
450
|
class InstantiatedUnnestJoin(BaseModel):
|
|
450
|
-
|
|
451
|
+
object_to_unnest: BuildConcept | BuildParamaterizedConceptReference | BuildFunction
|
|
451
452
|
alias: str = "unnest"
|
|
452
453
|
|
|
453
454
|
|
trilogy/core/optimization.py
CHANGED
|
@@ -5,7 +5,6 @@ from trilogy.core.models.build import (
|
|
|
5
5
|
)
|
|
6
6
|
from trilogy.core.models.execute import CTE, UnionCTE
|
|
7
7
|
from trilogy.core.optimizations import (
|
|
8
|
-
InlineConstant,
|
|
9
8
|
InlineDatasource,
|
|
10
9
|
OptimizationRule,
|
|
11
10
|
PredicatePushdown,
|
|
@@ -206,8 +205,6 @@ def optimize_ctes(
|
|
|
206
205
|
REGISTERED_RULES.append(PredicatePushdown())
|
|
207
206
|
if CONFIG.optimizations.predicate_pushdown:
|
|
208
207
|
REGISTERED_RULES.append(PredicatePushdownRemove())
|
|
209
|
-
if CONFIG.optimizations.constant_inlining:
|
|
210
|
-
REGISTERED_RULES.append(InlineConstant())
|
|
211
208
|
for rule in REGISTERED_RULES:
|
|
212
209
|
loops = 0
|
|
213
210
|
complete = False
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
from .base_optimization import OptimizationRule
|
|
2
|
-
from .inline_constant import InlineConstant
|
|
3
2
|
from .inline_datasource import InlineDatasource
|
|
4
3
|
from .predicate_pushdown import PredicatePushdown, PredicatePushdownRemove
|
|
5
4
|
|
|
6
5
|
__all__ = [
|
|
7
6
|
"OptimizationRule",
|
|
8
|
-
"InlineConstant",
|
|
9
7
|
"InlineDatasource",
|
|
10
8
|
"PredicatePushdown",
|
|
11
9
|
"PredicatePushdownRemove",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
3
|
from trilogy.constants import logger
|
|
4
|
+
from trilogy.core.internal import ALL_ROWS_CONCEPT
|
|
4
5
|
from trilogy.core.models.build import (
|
|
5
6
|
BuildAggregateWrapper,
|
|
6
7
|
BuildConcept,
|
|
@@ -92,7 +93,9 @@ def gen_group_node(
|
|
|
92
93
|
logger.info(
|
|
93
94
|
f"{padding(depth)}{LOGGER_PREFIX} fetching group node parents {LooseBuildConceptList(concepts=parent_concepts)}"
|
|
94
95
|
)
|
|
95
|
-
parent_concepts = unique(
|
|
96
|
+
parent_concepts = unique(
|
|
97
|
+
[x for x in parent_concepts if not x.name == ALL_ROWS_CONCEPT], "address"
|
|
98
|
+
)
|
|
96
99
|
parent = source_concepts(
|
|
97
100
|
mandatory_list=parent_concepts,
|
|
98
101
|
environment=environment,
|
|
@@ -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
|
@@ -12,7 +12,9 @@ from trilogy.core.models.build import (
|
|
|
12
12
|
BuildConcept,
|
|
13
13
|
BuildConditional,
|
|
14
14
|
BuildDatasource,
|
|
15
|
+
BuildFunction,
|
|
15
16
|
BuildMultiSelectLineage,
|
|
17
|
+
BuildParamaterizedConceptReference,
|
|
16
18
|
BuildSelectLineage,
|
|
17
19
|
Factory,
|
|
18
20
|
)
|
|
@@ -55,8 +57,14 @@ def base_join_to_join(
|
|
|
55
57
|
"""This function converts joins at the datasource level
|
|
56
58
|
to joins at the CTE level"""
|
|
57
59
|
if isinstance(base_join, UnnestJoin):
|
|
60
|
+
object_to_unnest = base_join.parent.arguments[0]
|
|
61
|
+
if not isinstance(
|
|
62
|
+
object_to_unnest,
|
|
63
|
+
(BuildConcept | BuildParamaterizedConceptReference | BuildFunction),
|
|
64
|
+
):
|
|
65
|
+
raise ValueError(f"Unnest join must be a concept; got {object_to_unnest}")
|
|
58
66
|
return InstantiatedUnnestJoin(
|
|
59
|
-
|
|
67
|
+
object_to_unnest=object_to_unnest,
|
|
60
68
|
alias=base_join.alias,
|
|
61
69
|
)
|
|
62
70
|
|
|
@@ -220,6 +228,8 @@ def resolve_cte_base_name_and_alias_v2(
|
|
|
220
228
|
source_map: Dict[str, list[str]],
|
|
221
229
|
raw_joins: List[Join | InstantiatedUnnestJoin],
|
|
222
230
|
) -> Tuple[str | None, str | None]:
|
|
231
|
+
if not source.datasources:
|
|
232
|
+
return None, None
|
|
223
233
|
if (
|
|
224
234
|
isinstance(source.datasources[0], BuildDatasource)
|
|
225
235
|
and not source.datasources[0].name == CONSTANT_DATASET
|
|
@@ -301,18 +311,23 @@ def datasource_to_cte(
|
|
|
301
311
|
|
|
302
312
|
else:
|
|
303
313
|
# source is the first datasource of the query datasource
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
314
|
+
if query_datasource.datasources:
|
|
315
|
+
|
|
316
|
+
source = query_datasource.datasources[0]
|
|
317
|
+
# this is required to ensure that constant datasets
|
|
318
|
+
# render properly on initial access; since they have
|
|
319
|
+
# no actual source
|
|
320
|
+
if source.name == CONSTANT_DATASET:
|
|
321
|
+
source_map = {k: [] for k in query_datasource.source_map}
|
|
322
|
+
existence_map = source_map
|
|
323
|
+
else:
|
|
324
|
+
source_map = {
|
|
325
|
+
k: [] if not v else [source.safe_identifier]
|
|
326
|
+
for k, v in query_datasource.source_map.items()
|
|
327
|
+
}
|
|
328
|
+
existence_map = source_map
|
|
311
329
|
else:
|
|
312
|
-
source_map = {
|
|
313
|
-
k: [] if not v else [source.safe_identifier]
|
|
314
|
-
for k, v in query_datasource.source_map.items()
|
|
315
|
-
}
|
|
330
|
+
source_map = {k: [] for k in query_datasource.source_map}
|
|
316
331
|
existence_map = source_map
|
|
317
332
|
|
|
318
333
|
human_id = generate_cte_name(query_datasource.identifier, name_map)
|
|
@@ -373,10 +388,9 @@ def get_query_node(
|
|
|
373
388
|
) -> StrategyNode:
|
|
374
389
|
if not statement.output_components:
|
|
375
390
|
raise ValueError(f"Statement has no output components {statement}")
|
|
376
|
-
|
|
377
391
|
history = history or History(base_environment=environment)
|
|
378
392
|
build_statement: BuildSelectLineage | BuildMultiSelectLineage = Factory(
|
|
379
|
-
environment=environment
|
|
393
|
+
environment=environment,
|
|
380
394
|
).build(statement)
|
|
381
395
|
|
|
382
396
|
# 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
|
@@ -3,7 +3,13 @@ from typing import Any, Callable, Dict, List, Optional, Sequence, Union
|
|
|
3
3
|
|
|
4
4
|
from jinja2 import Template
|
|
5
5
|
|
|
6
|
-
from trilogy.constants import
|
|
6
|
+
from trilogy.constants import (
|
|
7
|
+
CONFIG,
|
|
8
|
+
DEFAULT_NAMESPACE,
|
|
9
|
+
MagicConstants,
|
|
10
|
+
Rendering,
|
|
11
|
+
logger,
|
|
12
|
+
)
|
|
7
13
|
from trilogy.core.enums import (
|
|
8
14
|
DatePart,
|
|
9
15
|
FunctionType,
|
|
@@ -22,6 +28,7 @@ from trilogy.core.models.build import (
|
|
|
22
28
|
BuildFunction,
|
|
23
29
|
BuildMultiSelectLineage,
|
|
24
30
|
BuildOrderItem,
|
|
31
|
+
BuildParamaterizedConceptReference,
|
|
25
32
|
BuildParenthetical,
|
|
26
33
|
BuildRowsetItem,
|
|
27
34
|
BuildSubselectComparison,
|
|
@@ -199,6 +206,7 @@ FUNCTION_MAP = {
|
|
|
199
206
|
FunctionType.DATE_TRUNCATE: lambda x: f"date_trunc({x[0]},{x[1]})",
|
|
200
207
|
FunctionType.DATE_PART: lambda x: f"date_part({x[0]},{x[1]})",
|
|
201
208
|
FunctionType.DATE_ADD: lambda x: f"date_add({x[0]},{x[1]}, {x[2]})",
|
|
209
|
+
FunctionType.DATE_SUB: lambda x: f"date_sub({x[0]},{x[1]}, {x[2]})",
|
|
202
210
|
FunctionType.DATE_DIFF: lambda x: f"date_diff({x[0]},{x[1]}, {x[2]})",
|
|
203
211
|
FunctionType.DATE: lambda x: f"date({x[0]})",
|
|
204
212
|
FunctionType.DATETIME: lambda x: f"datetime({x[0]})",
|
|
@@ -512,6 +520,9 @@ class BaseDialect:
|
|
|
512
520
|
BuildWindowItem,
|
|
513
521
|
BuildFilterItem,
|
|
514
522
|
BuildParenthetical,
|
|
523
|
+
BuildParamaterizedConceptReference,
|
|
524
|
+
BuildMultiSelectLineage,
|
|
525
|
+
BuildRowsetItem,
|
|
515
526
|
str,
|
|
516
527
|
int,
|
|
517
528
|
list,
|
|
@@ -692,6 +703,14 @@ class BaseDialect:
|
|
|
692
703
|
return self.render_expr(e.type, cte=cte, cte_map=cte_map)
|
|
693
704
|
elif isinstance(e, ListType):
|
|
694
705
|
return f"{self.COMPLEX_DATATYPE_MAP[DataType.LIST](self.render_expr(e.value_data_type, cte=cte, cte_map=cte_map))}"
|
|
706
|
+
elif isinstance(e, BuildParamaterizedConceptReference):
|
|
707
|
+
if self.rendering.parameters:
|
|
708
|
+
if e.concept.namespace == DEFAULT_NAMESPACE:
|
|
709
|
+
return f":{e.concept.name}"
|
|
710
|
+
return f":{e.concept.address}"
|
|
711
|
+
elif e.concept.lineage:
|
|
712
|
+
return self.render_expr(e.concept.lineage, cte=cte, cte_map=cte_map)
|
|
713
|
+
return f"{self.QUOTE_CHARACTER}{e.concept.address}{self.QUOTE_CHARACTER}"
|
|
695
714
|
else:
|
|
696
715
|
raise ValueError(f"Unable to render type {type(e)} {e}")
|
|
697
716
|
|
|
@@ -743,12 +762,12 @@ class BaseDialect:
|
|
|
743
762
|
UnnestMode.CROSS_APPLY,
|
|
744
763
|
):
|
|
745
764
|
|
|
746
|
-
source = f"{render_unnest(self.UNNEST_MODE, self.QUOTE_CHARACTER, cte.join_derived_concepts[0], self.
|
|
765
|
+
source = f"{render_unnest(self.UNNEST_MODE, self.QUOTE_CHARACTER, cte.join_derived_concepts[0], self.render_expr, cte)}"
|
|
747
766
|
elif (
|
|
748
767
|
cte.join_derived_concepts
|
|
749
768
|
and self.UNNEST_MODE == UnnestMode.SNOWFLAKE
|
|
750
769
|
):
|
|
751
|
-
source = f"{render_unnest(self.UNNEST_MODE, self.QUOTE_CHARACTER, cte.join_derived_concepts[0], self.
|
|
770
|
+
source = f"{render_unnest(self.UNNEST_MODE, self.QUOTE_CHARACTER, cte.join_derived_concepts[0], self.render_expr, cte)}"
|
|
752
771
|
# direct - eg DUCK DB - can be directly selected inline
|
|
753
772
|
elif (
|
|
754
773
|
cte.join_derived_concepts and self.UNNEST_MODE == UnnestMode.DIRECT
|
|
@@ -802,7 +821,6 @@ class BaseDialect:
|
|
|
802
821
|
render_join(
|
|
803
822
|
join,
|
|
804
823
|
self.QUOTE_CHARACTER,
|
|
805
|
-
self.render_concept_sql,
|
|
806
824
|
self.render_expr,
|
|
807
825
|
cte,
|
|
808
826
|
self.UNNEST_MODE,
|
trilogy/dialect/common.py
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
from typing import Callable
|
|
2
2
|
|
|
3
3
|
from trilogy.core.enums import Modifier, UnnestMode
|
|
4
|
-
from trilogy.core.models.build import
|
|
4
|
+
from trilogy.core.models.build import (
|
|
5
|
+
BuildConcept,
|
|
6
|
+
BuildFunction,
|
|
7
|
+
BuildParamaterizedConceptReference,
|
|
8
|
+
)
|
|
5
9
|
from trilogy.core.models.datasource import RawColumnExpr
|
|
6
10
|
from trilogy.core.models.execute import (
|
|
7
11
|
CTE,
|
|
@@ -19,21 +23,27 @@ def null_wrapper(lval: str, rval: str, modifiers: list[Modifier]) -> str:
|
|
|
19
23
|
def render_unnest(
|
|
20
24
|
unnest_mode: UnnestMode,
|
|
21
25
|
quote_character: str,
|
|
22
|
-
concept: BuildConcept,
|
|
23
|
-
render_func: Callable[
|
|
26
|
+
concept: BuildConcept | BuildParamaterizedConceptReference | BuildFunction,
|
|
27
|
+
render_func: Callable[
|
|
28
|
+
[BuildConcept | BuildParamaterizedConceptReference | BuildFunction, CTE], str
|
|
29
|
+
],
|
|
24
30
|
cte: CTE,
|
|
25
31
|
):
|
|
32
|
+
if not isinstance(concept, (BuildConcept, BuildParamaterizedConceptReference)):
|
|
33
|
+
address = "anon_function"
|
|
34
|
+
else:
|
|
35
|
+
address = concept.safe_address
|
|
26
36
|
if unnest_mode == UnnestMode.CROSS_JOIN:
|
|
27
|
-
return f"{render_func(concept, cte
|
|
37
|
+
return f"{render_func(concept, cte)} as {quote_character}{address}{quote_character}"
|
|
28
38
|
elif unnest_mode == UnnestMode.CROSS_JOIN_ALIAS:
|
|
29
|
-
return f"{render_func(concept, cte
|
|
39
|
+
return f"{render_func(concept, cte)} as unnest_wrapper ({quote_character}{address}{quote_character})"
|
|
30
40
|
elif unnest_mode == UnnestMode.SNOWFLAKE:
|
|
31
41
|
# if we don't actually have a join, we're directly unnesting a concept, and we can skip the flatten
|
|
32
42
|
if not cte.render_from_clause:
|
|
33
|
-
return f"{render_func(concept, cte
|
|
43
|
+
return f"{render_func(concept, cte)} as unnest_wrapper ( unnest1, unnest2, unnest3, unnest4, {quote_character}{cte.join_derived_concepts[0].safe_address}{quote_character})"
|
|
34
44
|
# otherwise, flatten the concept for the join
|
|
35
|
-
return f"flatten({render_func(concept, cte
|
|
36
|
-
return f"{render_func(concept, cte
|
|
45
|
+
return f"flatten({render_func(concept, cte)}) as unnest_wrapper ( unnest1, unnest2, unnest3, unnest4, {quote_character}{cte.join_derived_concepts[0].safe_address}{quote_character})"
|
|
46
|
+
return f"{render_func(concept, cte)} as {quote_character}{address}{quote_character}"
|
|
37
47
|
|
|
38
48
|
|
|
39
49
|
def render_join_concept(
|
|
@@ -60,8 +70,9 @@ def render_join_concept(
|
|
|
60
70
|
def render_join(
|
|
61
71
|
join: Join | InstantiatedUnnestJoin,
|
|
62
72
|
quote_character: str,
|
|
63
|
-
|
|
64
|
-
|
|
73
|
+
render_expr_func: Callable[
|
|
74
|
+
[BuildConcept | BuildParamaterizedConceptReference | BuildFunction, CTE], str
|
|
75
|
+
],
|
|
65
76
|
cte: CTE,
|
|
66
77
|
unnest_mode: UnnestMode = UnnestMode.CROSS_APPLY,
|
|
67
78
|
) -> str | None:
|
|
@@ -72,12 +83,12 @@ def render_join(
|
|
|
72
83
|
if not cte:
|
|
73
84
|
raise ValueError("must provide a cte to build an unnest joins")
|
|
74
85
|
if unnest_mode == UnnestMode.CROSS_JOIN:
|
|
75
|
-
return f"CROSS JOIN {render_unnest(unnest_mode, quote_character, join.
|
|
86
|
+
return f"CROSS JOIN {render_unnest(unnest_mode, quote_character, join.object_to_unnest, render_expr_func, cte)}"
|
|
76
87
|
if unnest_mode == UnnestMode.CROSS_JOIN_ALIAS:
|
|
77
|
-
return f"CROSS JOIN {render_unnest(unnest_mode, quote_character, join.
|
|
88
|
+
return f"CROSS JOIN {render_unnest(unnest_mode, quote_character, join.object_to_unnest, render_expr_func, cte)}"
|
|
78
89
|
if unnest_mode == UnnestMode.SNOWFLAKE:
|
|
79
|
-
return f"LEFT JOIN LATERAL {render_unnest(unnest_mode, quote_character, join.
|
|
80
|
-
return f"FULL JOIN {render_unnest(unnest_mode, quote_character, join.
|
|
90
|
+
return f"LEFT JOIN LATERAL {render_unnest(unnest_mode, quote_character, join.object_to_unnest, render_expr_func, cte)}"
|
|
91
|
+
return f"FULL JOIN {render_unnest(unnest_mode, quote_character, join.object_to_unnest, render_expr_func, cte)}"
|
|
81
92
|
# left_name = join.left_name
|
|
82
93
|
right_name = join.right_name
|
|
83
94
|
if cte.quote_address.get(join.right_name, False):
|
trilogy/executor.py
CHANGED
|
@@ -394,6 +394,9 @@ class Executor(object):
|
|
|
394
394
|
if isinstance(rval, ListWrapper):
|
|
395
395
|
return [x for x in rval]
|
|
396
396
|
if isinstance(rval, MapWrapper):
|
|
397
|
+
# duckdb expects maps in this format as variables
|
|
398
|
+
if self.dialect == Dialects.DUCK_DB:
|
|
399
|
+
return {"key": [x for x in rval], "value": [rval[x] for x in rval]}
|
|
397
400
|
return {k: v for k, v in rval.items()}
|
|
398
401
|
# if isinstance(rval, ConceptRef):
|
|
399
402
|
# return self._concept_to_value(self.environment.concepts[rval.address], local_concepts=local_concepts)
|
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,52 @@ 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
|
+
for arg in atom.arguments:
|
|
215
|
+
relevant = relevant or atom_is_relevant(arg, others, environment)
|
|
216
|
+
return relevant
|
|
217
|
+
elif isinstance(atom, FunctionCallWrapper):
|
|
218
|
+
return any(
|
|
219
|
+
[atom_is_relevant(atom.content, others, environment)]
|
|
220
|
+
+ [atom_is_relevant(x, others, environment) for x in atom.args]
|
|
221
|
+
)
|
|
222
|
+
elif isinstance(atom, ConceptArgs):
|
|
223
|
+
# use atom is relevant here to trigger the early exit behavior for concpets in set
|
|
224
|
+
return any(
|
|
225
|
+
[atom_is_relevant(x, others, environment) for x in atom.concept_arguments]
|
|
226
|
+
)
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
|
|
194
230
|
def concept_is_relevant(
|
|
195
231
|
concept: Concept | ConceptRef,
|
|
196
232
|
others: list[Concept | ConceptRef],
|
|
197
233
|
environment: Environment | None = None,
|
|
198
234
|
) -> bool:
|
|
199
|
-
if isinstance(concept, UndefinedConcept):
|
|
200
235
|
|
|
236
|
+
if isinstance(concept, UndefinedConcept):
|
|
201
237
|
return False
|
|
202
238
|
if concept.datatype == DataType.UNKNOWN:
|
|
203
239
|
return False
|
|
240
|
+
|
|
204
241
|
if isinstance(concept, ConceptRef):
|
|
205
242
|
if environment:
|
|
206
243
|
concept = environment.concepts[concept.address]
|
|
@@ -208,41 +245,56 @@ def concept_is_relevant(
|
|
|
208
245
|
raise SyntaxError(
|
|
209
246
|
"Require environment to determine relevance of ConceptRef"
|
|
210
247
|
)
|
|
211
|
-
|
|
248
|
+
if concept.derivation == Derivation.CONSTANT:
|
|
249
|
+
return False
|
|
212
250
|
if concept.is_aggregate and not (
|
|
213
251
|
isinstance(concept.lineage, AggregateWrapper) and concept.lineage.by
|
|
214
252
|
):
|
|
215
253
|
|
|
216
254
|
return False
|
|
217
255
|
if concept.purpose in (Purpose.PROPERTY, Purpose.METRIC) and concept.keys:
|
|
218
|
-
if
|
|
219
|
-
|
|
256
|
+
if all([c in others for c in concept.keys]):
|
|
220
257
|
return False
|
|
258
|
+
if (
|
|
259
|
+
concept.purpose == Purpose.KEY
|
|
260
|
+
and concept.keys
|
|
261
|
+
and all([c in others for c in concept.keys])
|
|
262
|
+
):
|
|
263
|
+
return False
|
|
221
264
|
if concept.purpose in (Purpose.METRIC,):
|
|
222
265
|
if all([c in others for c in concept.grain.components]):
|
|
223
266
|
return False
|
|
224
|
-
if concept.derivation in (Derivation.BASIC,)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
267
|
+
if concept.derivation in (Derivation.BASIC,) and isinstance(
|
|
268
|
+
concept.lineage, Function
|
|
269
|
+
):
|
|
270
|
+
relevant = False
|
|
271
|
+
for arg in concept.lineage.arguments:
|
|
272
|
+
relevant = atom_is_relevant(arg, others, environment) or relevant
|
|
273
|
+
return relevant
|
|
229
274
|
if concept.granularity == Granularity.SINGLE_ROW:
|
|
230
275
|
return False
|
|
231
276
|
return True
|
|
232
277
|
|
|
233
278
|
|
|
234
279
|
def concepts_to_grain_concepts(
|
|
235
|
-
concepts: Iterable[Concept | ConceptRef | str],
|
|
280
|
+
concepts: Iterable[Concept | ConceptRef | str],
|
|
281
|
+
environment: Environment | None,
|
|
282
|
+
local_concepts: dict[str, Concept] | None = None,
|
|
236
283
|
) -> list[Concept]:
|
|
237
284
|
pconcepts: list[Concept] = []
|
|
238
285
|
for c in concepts:
|
|
239
|
-
|
|
240
286
|
if isinstance(c, Concept):
|
|
241
287
|
pconcepts.append(c)
|
|
242
288
|
elif isinstance(c, ConceptRef) and environment:
|
|
243
|
-
|
|
289
|
+
if local_concepts and c.address in local_concepts:
|
|
290
|
+
pconcepts.append(local_concepts[c.address])
|
|
291
|
+
else:
|
|
292
|
+
pconcepts.append(environment.concepts[c.address])
|
|
244
293
|
elif isinstance(c, str) and environment:
|
|
245
|
-
|
|
294
|
+
if local_concepts and c in local_concepts:
|
|
295
|
+
pconcepts.append(local_concepts[c])
|
|
296
|
+
else:
|
|
297
|
+
pconcepts.append(environment.concepts[c])
|
|
246
298
|
else:
|
|
247
299
|
raise ValueError(
|
|
248
300
|
f"Unable to resolve input {c} without environment provided to concepts_to_grain call"
|
|
@@ -250,6 +302,7 @@ def concepts_to_grain_concepts(
|
|
|
250
302
|
|
|
251
303
|
final: List[Concept] = []
|
|
252
304
|
for sub in pconcepts:
|
|
305
|
+
|
|
253
306
|
if not concept_is_relevant(sub, pconcepts, environment): # type: ignore
|
|
254
307
|
continue
|
|
255
308
|
final.append(sub)
|
|
@@ -366,7 +419,12 @@ def function_to_concept(
|
|
|
366
419
|
is_metric = False
|
|
367
420
|
ref_args, is_metric = get_relevant_parent_concepts(parent)
|
|
368
421
|
concrete_args = [environment.concepts[c.address] for c in ref_args]
|
|
369
|
-
pkeys += [
|
|
422
|
+
pkeys += [
|
|
423
|
+
x
|
|
424
|
+
for x in concrete_args
|
|
425
|
+
if not x.derivation == Derivation.CONSTANT
|
|
426
|
+
and not (x.derivation == Derivation.AGGREGATE and not x.grain.components)
|
|
427
|
+
]
|
|
370
428
|
grain: Grain | None = Grain()
|
|
371
429
|
for x in pkeys:
|
|
372
430
|
grain += x.grain
|
|
@@ -376,7 +434,7 @@ def function_to_concept(
|
|
|
376
434
|
modifiers = get_upstream_modifiers(pkeys, environment)
|
|
377
435
|
key_grain: list[str] = []
|
|
378
436
|
for x in pkeys:
|
|
379
|
-
# metrics will group to keys, so do
|
|
437
|
+
# metrics will group to keys, so do not do key traversal
|
|
380
438
|
if is_metric:
|
|
381
439
|
key_grain.append(x.address)
|
|
382
440
|
# otherwse, for row ops, assume keys are transitive
|
|
@@ -419,7 +477,6 @@ def function_to_concept(
|
|
|
419
477
|
else:
|
|
420
478
|
derivation = Derivation.BASIC
|
|
421
479
|
granularity = Granularity.MULTI_ROW
|
|
422
|
-
|
|
423
480
|
if grain is not None:
|
|
424
481
|
r = Concept(
|
|
425
482
|
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)
|
|
@@ -1792,7 +1795,7 @@ class ParseToObjects(Transformer):
|
|
|
1792
1795
|
def fyear(self, meta, args):
|
|
1793
1796
|
return self.function_factory.create_function(args, FunctionType.YEAR, meta)
|
|
1794
1797
|
|
|
1795
|
-
def internal_fcast(self, meta, args):
|
|
1798
|
+
def internal_fcast(self, meta, args) -> Function:
|
|
1796
1799
|
args = process_function_args(args, meta=meta, environment=self.environment)
|
|
1797
1800
|
if isinstance(args[0], str):
|
|
1798
1801
|
processed: date | datetime | int | float | bool | str
|
|
@@ -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 ")"
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
from trilogy.core.enums import Derivation
|
|
2
|
-
from trilogy.core.models.build import BuildConcept
|
|
3
|
-
from trilogy.core.models.execute import (
|
|
4
|
-
CTE,
|
|
5
|
-
UnionCTE,
|
|
6
|
-
)
|
|
7
|
-
from trilogy.core.optimizations.base_optimization import OptimizationRule
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class InlineConstant(OptimizationRule):
|
|
11
|
-
def optimize(
|
|
12
|
-
self, cte: CTE | UnionCTE, inverse_map: dict[str, list[CTE | UnionCTE]]
|
|
13
|
-
) -> bool:
|
|
14
|
-
if isinstance(cte, UnionCTE):
|
|
15
|
-
return any(self.optimize(x, inverse_map) for x in cte.internal_ctes)
|
|
16
|
-
|
|
17
|
-
to_inline: list[BuildConcept] = []
|
|
18
|
-
for x in cte.source.input_concepts:
|
|
19
|
-
if x.address not in cte.source_map:
|
|
20
|
-
continue
|
|
21
|
-
if x.derivation == Derivation.CONSTANT:
|
|
22
|
-
self.log(f"Found constant {x.address} on {cte.name}")
|
|
23
|
-
to_inline.append(x)
|
|
24
|
-
if to_inline:
|
|
25
|
-
inlined = False
|
|
26
|
-
for c in to_inline:
|
|
27
|
-
self.log(f"Attempting to inline constant {c.address} on {cte.name}")
|
|
28
|
-
test = cte.inline_constant(c)
|
|
29
|
-
if test:
|
|
30
|
-
self.log(f"Successfully inlined constant {c.address} to {cte.name}")
|
|
31
|
-
inlined = True
|
|
32
|
-
else:
|
|
33
|
-
self.log(f"Could not inline constant to {cte.name}")
|
|
34
|
-
return inlined
|
|
35
|
-
return False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|