pytrilogy 0.0.3.56__py3-none-any.whl → 0.0.3.60__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.56.dist-info → pytrilogy-0.0.3.60.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.3.56.dist-info → pytrilogy-0.0.3.60.dist-info}/RECORD +33 -32
- {pytrilogy-0.0.3.56.dist-info → pytrilogy-0.0.3.60.dist-info}/WHEEL +1 -1
- trilogy/__init__.py +1 -1
- trilogy/authoring/__init__.py +12 -1
- trilogy/core/constants.py +1 -0
- trilogy/core/enums.py +1 -0
- trilogy/core/models/execute.py +4 -1
- trilogy/core/optimization.py +4 -4
- trilogy/core/processing/concept_strategies_v3.py +4 -3
- trilogy/core/processing/discovery_node_factory.py +9 -3
- trilogy/core/processing/node_generators/basic_node.py +29 -11
- trilogy/core/processing/node_generators/node_merge_node.py +16 -8
- trilogy/core/processing/node_generators/synonym_node.py +2 -1
- trilogy/core/processing/node_generators/unnest_node.py +7 -1
- trilogy/core/processing/nodes/base_node.py +0 -13
- trilogy/core/processing/nodes/group_node.py +1 -1
- trilogy/core/processing/utility.py +38 -11
- trilogy/core/query_processor.py +3 -3
- trilogy/core/statements/author.py +6 -2
- trilogy/core/statements/execute.py +3 -2
- trilogy/dialect/base.py +21 -30
- trilogy/dialect/bigquery.py +2 -2
- trilogy/dialect/common.py +10 -4
- trilogy/executor.py +13 -4
- trilogy/parsing/common.py +7 -2
- trilogy/parsing/parse_engine.py +8 -2
- trilogy/parsing/trilogy.lark +1 -1
- trilogy/std/date.preql +4 -1
- trilogy/std/ranking.preql +6 -0
- {pytrilogy-0.0.3.56.dist-info → pytrilogy-0.0.3.60.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.3.56.dist-info → pytrilogy-0.0.3.60.dist-info}/licenses/LICENSE.md +0 -0
- {pytrilogy-0.0.3.56.dist-info → pytrilogy-0.0.3.60.dist-info}/top_level.txt +0 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
pytrilogy-0.0.3.
|
|
2
|
-
trilogy/__init__.py,sha256=
|
|
1
|
+
pytrilogy-0.0.3.60.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
2
|
+
trilogy/__init__.py,sha256=8jDnsd42NfWTeVpYr5aVIC5F7YtNxUA3qJ-UpkKPzbU,303
|
|
3
3
|
trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
trilogy/constants.py,sha256=lv_aJWP6dn6e2aF4BAE72jbnNtceFddfqtiDSsvzno0,1692
|
|
5
5
|
trilogy/engine.py,sha256=OK2RuqCIUId6yZ5hfF8J1nxGP0AJqHRZiafcowmW0xc,1728
|
|
6
|
-
trilogy/executor.py,sha256=
|
|
6
|
+
trilogy/executor.py,sha256=BolR6UwgDOdIcDG0gw_OSaB23rISgIn8Dzdll0kODmg,16506
|
|
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
|
|
10
10
|
trilogy/utility.py,sha256=euQccZLKoYBz0LNg5tzLlvv2YHvXh9HArnYp1V3uXsM,763
|
|
11
|
-
trilogy/authoring/__init__.py,sha256=
|
|
11
|
+
trilogy/authoring/__init__.py,sha256=h-Ag7vT76tsjib9BfjOgI-yVpuJDgpn2TSps-ibRAj8,2593
|
|
12
12
|
trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
trilogy/core/constants.py,sha256=
|
|
14
|
-
trilogy/core/enums.py,sha256=
|
|
13
|
+
trilogy/core/constants.py,sha256=nizWYDCJQ1bigQMtkNIEMNTcN0NoEAXiIHLzpelxQ24,201
|
|
14
|
+
trilogy/core/enums.py,sha256=z2mljNfhosnap4Nesx-mVOzM4UAPYU0R-Gy-HQVqcBI,7781
|
|
15
15
|
trilogy/core/env_processor.py,sha256=pFsxnluKIusGKx1z7tTnfsd_xZcPy9pZDungkjkyvI0,3170
|
|
16
16
|
trilogy/core/environment_helpers.py,sha256=VvPIiFemqaLLpIpLIqprfu63K7muZ1YzNg7UZIUph8w,8267
|
|
17
17
|
trilogy/core/ergonomics.py,sha256=e-7gE29vPLFdg0_A1smQ7eOrUwKl5VYdxRSTddHweRA,1631
|
|
@@ -19,8 +19,8 @@ trilogy/core/exceptions.py,sha256=JPYyBcit3T_pRtlHdtKSeVJkIyWUTozW2aaut25A2xI,67
|
|
|
19
19
|
trilogy/core/functions.py,sha256=poVfAwet1xdxTkC7WL38UmGRDpUVO9iSMNWSagl9_r4,29302
|
|
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=lE5WSTsDHOaqvh-G9bOfbegP3R7NDqD4jrD3VAYxEHY,8853
|
|
23
|
+
trilogy/core/query_processor.py,sha256=QiE_w5HgheT4GLZFnaLssJ4plf4voK0TeTd6N3jhR6A,20188
|
|
24
24
|
trilogy/core/utility.py,sha256=3VC13uSQWcZNghgt7Ot0ZTeEmNqs__cx122abVq9qhM,410
|
|
25
25
|
trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
26
|
trilogy/core/models/author.py,sha256=8XbIsQr6cQrgo9uzee5qRoYiMdEG7yKF4FiiWImW7U0,77490
|
|
@@ -29,41 +29,41 @@ trilogy/core/models/build_environment.py,sha256=s_C9xAHuD3yZ26T15pWVBvoqvlp2LdZ8
|
|
|
29
29
|
trilogy/core/models/core.py,sha256=wx6hJcFECMG-Ij972ADNkr-3nFXkYESr82ObPiC46_U,10875
|
|
30
30
|
trilogy/core/models/datasource.py,sha256=6RjJUd2u4nYmEwFBpJlM9LbHVYDv8iHJxqiBMZqUrwI,9422
|
|
31
31
|
trilogy/core/models/environment.py,sha256=AVSrvjNcNX535GhCPtYhCRY2Lp_Hj0tdY3VVt_kZb9Q,27260
|
|
32
|
-
trilogy/core/models/execute.py,sha256=
|
|
32
|
+
trilogy/core/models/execute.py,sha256=hOilC-lka4W-C8Pakb0Vd1-T0oskeWdC8Ls0bm8_388,43109
|
|
33
33
|
trilogy/core/optimizations/__init__.py,sha256=YH2-mGXZnVDnBcWVi8vTbrdw7Qs5TivG4h38rH3js_I,290
|
|
34
34
|
trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnbnb6vdVIztUfFQZsGnM,513
|
|
35
35
|
trilogy/core/optimizations/inline_datasource.py,sha256=2sWNRpoRInnTgo9wExVT_r9RfLAQHI57reEV5cGHUcg,4329
|
|
36
36
|
trilogy/core/optimizations/predicate_pushdown.py,sha256=g4AYE8Aw_iMlAh68TjNXGP754NTurrDduFECkUjoBnc,9399
|
|
37
37
|
trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
-
trilogy/core/processing/concept_strategies_v3.py,sha256=
|
|
38
|
+
trilogy/core/processing/concept_strategies_v3.py,sha256=KHpSQyG5Ubb5H1dmzpDWI2ypJL_rxeS3zc3sjaeCq_s,21997
|
|
39
39
|
trilogy/core/processing/discovery_loop.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
|
-
trilogy/core/processing/discovery_node_factory.py,sha256=
|
|
40
|
+
trilogy/core/processing/discovery_node_factory.py,sha256=LFGHUznUz_kszcs8QywZ0pNaejbRoNVHv81gX6Mqm2Y,14909
|
|
41
41
|
trilogy/core/processing/discovery_utility.py,sha256=hF3aUbRHHZFeFT5aBjE6TuSeU60I90gzmj512QXG_t8,4856
|
|
42
42
|
trilogy/core/processing/discovery_validation.py,sha256=Ek9jviFgimLMUMYLXBChUQmOD94ihhwQ3NDVe6RTdWg,4930
|
|
43
43
|
trilogy/core/processing/graph_utils.py,sha256=8QUVrkE9j-9C1AyrCb1nQEh8daCe0u1HuXl-Te85lag,1205
|
|
44
|
-
trilogy/core/processing/utility.py,sha256=
|
|
44
|
+
trilogy/core/processing/utility.py,sha256=mrfR9pgek-xjxoDQSlvPqOW9dpmREjgzqn4AGoqpGeM,22774
|
|
45
45
|
trilogy/core/processing/node_generators/__init__.py,sha256=w8TQQgNhyAra6JQHdg1_Ags4BGyxjXYruu6UeC5yOkI,873
|
|
46
|
-
trilogy/core/processing/node_generators/basic_node.py,sha256=
|
|
46
|
+
trilogy/core/processing/node_generators/basic_node.py,sha256=kyOAiVaYnUlWWRs4y6ctQPlisQ2kCRZ1wZYmJfbkRSw,4337
|
|
47
47
|
trilogy/core/processing/node_generators/common.py,sha256=PdysdroW9DUADP7f5Wv_GKPUyCTROZV1g3L45fawxi8,9443
|
|
48
48
|
trilogy/core/processing/node_generators/filter_node.py,sha256=0hdfiS2I-Jvr6P-il3jnAJK-g-DMG7_cFbZGCnLnJAo,10032
|
|
49
49
|
trilogy/core/processing/node_generators/group_node.py,sha256=nIfiMrJQEksUfqAeeA3X5PS1343y4lmPTipYuCa-rvs,6141
|
|
50
50
|
trilogy/core/processing/node_generators/group_to_node.py,sha256=jKcNCDOY6fNblrdZwaRU0sbUSr9H0moQbAxrGgX6iGA,3832
|
|
51
51
|
trilogy/core/processing/node_generators/multiselect_node.py,sha256=GWV5yLmKTe1yyPhN60RG1Rnrn4ktfn9lYYXi_FVU4UI,7061
|
|
52
|
-
trilogy/core/processing/node_generators/node_merge_node.py,sha256=
|
|
52
|
+
trilogy/core/processing/node_generators/node_merge_node.py,sha256=1vUmNE0qQQ1MYAvC6TO2dNsudIUytj9ZVcW4w1IGHXY,16734
|
|
53
53
|
trilogy/core/processing/node_generators/recursive_node.py,sha256=l5zdh0dURKwmAy8kK4OpMtZfyUEQRk6N-PwSWIyBpSM,2468
|
|
54
54
|
trilogy/core/processing/node_generators/rowset_node.py,sha256=2BiSsegbRF9csJ_Xl8P_CxIm4dAAb7dF29u6v_Odr-A,6709
|
|
55
55
|
trilogy/core/processing/node_generators/select_merge_node.py,sha256=lxXhMhDKGbu67QFNbbAT-BO8gbWppIvjn_hAXpLEPe0,19953
|
|
56
56
|
trilogy/core/processing/node_generators/select_node.py,sha256=3dvw0d53eUtCRCUPN6J48I3qBEX1Wha7saQ_ndPu6_I,1777
|
|
57
|
-
trilogy/core/processing/node_generators/synonym_node.py,sha256=
|
|
57
|
+
trilogy/core/processing/node_generators/synonym_node.py,sha256=a_RllD_5b4wg4JtiEkxJOfroFdEXJq6P4VUjga7sv5w,2300
|
|
58
58
|
trilogy/core/processing/node_generators/union_node.py,sha256=VNo6Oey4p8etU9xrOh2oTT2lIOTvY6PULUPRvVa2uxU,2877
|
|
59
|
-
trilogy/core/processing/node_generators/unnest_node.py,sha256=
|
|
59
|
+
trilogy/core/processing/node_generators/unnest_node.py,sha256=uWug51k3WtvFzj1uKyEoori0nDfvxeaiLbvf7ZmfYCM,2666
|
|
60
60
|
trilogy/core/processing/node_generators/window_node.py,sha256=GP3Hvkbb0TDA6ef7W7bmvQEHVH-NRIfBT_0W4fcH3g4,6529
|
|
61
61
|
trilogy/core/processing/node_generators/select_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
62
62
|
trilogy/core/processing/node_generators/select_helpers/datasource_injection.py,sha256=GMW07bb6hXurhF0hZLYoMAKSIS65tat5hwBjvqqPeSA,6516
|
|
63
63
|
trilogy/core/processing/nodes/__init__.py,sha256=97eFwBa9vBhhXszgO-9JVxUzejKKeSHG6DilxRCWRm0,6083
|
|
64
|
-
trilogy/core/processing/nodes/base_node.py,sha256=
|
|
64
|
+
trilogy/core/processing/nodes/base_node.py,sha256=p6yljFNLQsXz277c5wTATMNqsKUbsdP_3e7--tezBMw,17691
|
|
65
65
|
trilogy/core/processing/nodes/filter_node.py,sha256=5VtRfKbCORx0dV-vQfgy3gOEkmmscL9f31ExvlODwvY,2461
|
|
66
|
-
trilogy/core/processing/nodes/group_node.py,sha256=
|
|
66
|
+
trilogy/core/processing/nodes/group_node.py,sha256=4EbOur1wSsOpPvP6znHih126o6A-TWbBXyvhiw5B0rs,10505
|
|
67
67
|
trilogy/core/processing/nodes/merge_node.py,sha256=02oWRca0ba41U6PSAB14jwnWWxoyrvxRPLwkli259SY,15865
|
|
68
68
|
trilogy/core/processing/nodes/recursive_node.py,sha256=k0rizxR8KE64ievfHx_GPfQmU8QAP118Laeyq5BLUOk,1526
|
|
69
69
|
trilogy/core/processing/nodes/select_node_v2.py,sha256=Xyfq8lU7rP7JTAd8VV0ATDNal64n4xIBgWQsOuMe_Ak,8824
|
|
@@ -71,14 +71,14 @@ trilogy/core/processing/nodes/union_node.py,sha256=fDFzLAUh5876X6_NM7nkhoMvHEdGJ
|
|
|
71
71
|
trilogy/core/processing/nodes/unnest_node.py,sha256=oLKMMNMx6PLDPlt2V5neFMFrFWxET8r6XZElAhSNkO0,2181
|
|
72
72
|
trilogy/core/processing/nodes/window_node.py,sha256=JXJ0iVRlSEM2IBr1TANym2RaUf_p5E_l2sNykRzXWDo,1710
|
|
73
73
|
trilogy/core/statements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
74
|
-
trilogy/core/statements/author.py,sha256=
|
|
74
|
+
trilogy/core/statements/author.py,sha256=PES6GPOAt90EOb6pSJol7A_ChTSUuj740TOO60PG0Wg,15400
|
|
75
75
|
trilogy/core/statements/build.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
76
76
|
trilogy/core/statements/common.py,sha256=KxEmz2ySySyZ6CTPzn0fJl5NX2KOk1RPyuUSwWhnK1g,759
|
|
77
|
-
trilogy/core/statements/execute.py,sha256=
|
|
77
|
+
trilogy/core/statements/execute.py,sha256=rqfuoMuXPcH7L7TmE1dSiZ_K_A1ohB8whVMfGimZBOk,1294
|
|
78
78
|
trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
79
|
-
trilogy/dialect/base.py,sha256=
|
|
80
|
-
trilogy/dialect/bigquery.py,sha256=
|
|
81
|
-
trilogy/dialect/common.py,sha256=
|
|
79
|
+
trilogy/dialect/base.py,sha256=V9003GLDMf1fg-EhphlxKov84f1LKqhyYfnqujXblPg,42754
|
|
80
|
+
trilogy/dialect/bigquery.py,sha256=6ghCqy-k7UioIJc1EEQ7gRo_PHaO8Vm7yYbiQ-kgpzs,3629
|
|
81
|
+
trilogy/dialect/common.py,sha256=61yWE0K6M-Hfc934HkdHakIdLAUNB5kPSAQcTeAo3sU,5917
|
|
82
82
|
trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
|
|
83
83
|
trilogy/dialect/dataframe.py,sha256=RUbNgReEa9g3pL6H7fP9lPTrAij5pkqedpZ99D8_5AE,1522
|
|
84
84
|
trilogy/dialect/duckdb.py,sha256=C5TovwacDXo9YDpMTpPxkH7D0AxQERa7JL1RUkDGVng,3898
|
|
@@ -93,24 +93,25 @@ trilogy/hooks/graph_hook.py,sha256=c-vC-IXoJ_jDmKQjxQyIxyXPOuUcLIURB573gCsAfzQ,2
|
|
|
93
93
|
trilogy/hooks/query_debugger.py,sha256=1npRjww94sPV5RRBBlLqMJRaFkH9vhEY6o828MeoEcw,5583
|
|
94
94
|
trilogy/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
95
95
|
trilogy/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
96
|
-
trilogy/parsing/common.py,sha256=
|
|
96
|
+
trilogy/parsing/common.py,sha256=OOAssbo6Yp3L4u2OSGUYIuC_iAG_843vLpUtLkk1ub8,29960
|
|
97
97
|
trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
|
|
98
98
|
trilogy/parsing/exceptions.py,sha256=Xwwsv2C9kSNv2q-HrrKC1f60JNHShXcCMzstTSEbiCw,154
|
|
99
99
|
trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
100
|
-
trilogy/parsing/parse_engine.py,sha256=
|
|
100
|
+
trilogy/parsing/parse_engine.py,sha256=ioKWF6EFj1AIyg_pqPB9L6AjalkKQYoNav4cw4orWik,71368
|
|
101
101
|
trilogy/parsing/render.py,sha256=hI4y-xjXrEXvHslY2l2TQ8ic0zAOpN41ADH37J2_FZY,19047
|
|
102
|
-
trilogy/parsing/trilogy.lark,sha256=
|
|
102
|
+
trilogy/parsing/trilogy.lark,sha256=CK7Hqqr9W6FYb1BRWd9k3B6SzhJ8GgK4lu4hALxn_kw,14307
|
|
103
103
|
trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
104
104
|
trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
|
|
105
105
|
trilogy/std/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
106
|
-
trilogy/std/date.preql,sha256=
|
|
106
|
+
trilogy/std/date.preql,sha256=HWZm4t4HWyxr5geWRsY05RnHBVDMci8z8YA2cu0-OOw,188
|
|
107
107
|
trilogy/std/display.preql,sha256=2BbhvqR4rcltyAbOXAUo7SZ_yGFYZgFnurglHMbjW2g,40
|
|
108
108
|
trilogy/std/geography.preql,sha256=qLnHmDU5EnvjTbfqZF-NEclSYM5_e9rZra7QjV01rZ4,582
|
|
109
109
|
trilogy/std/money.preql,sha256=XWwvAV3WxBsHX9zfptoYRnBigcfYwrYtBHXTME0xJuQ,2082
|
|
110
110
|
trilogy/std/net.preql,sha256=-bMV6dyofskl4Kvows-iQ4JCxjVUwsZOeWCy8JO5Ftw,135
|
|
111
|
+
trilogy/std/ranking.preql,sha256=LDoZrYyz4g3xsII9XwXfmstZD-_92i1Eox1UqkBIfi8,83
|
|
111
112
|
trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
|
|
112
|
-
pytrilogy-0.0.3.
|
|
113
|
-
pytrilogy-0.0.3.
|
|
114
|
-
pytrilogy-0.0.3.
|
|
115
|
-
pytrilogy-0.0.3.
|
|
116
|
-
pytrilogy-0.0.3.
|
|
113
|
+
pytrilogy-0.0.3.60.dist-info/METADATA,sha256=n3i-LaY9KCLDvVzwfo7Z1xuUqYRPZR3LMXmOGFwBeGU,9095
|
|
114
|
+
pytrilogy-0.0.3.60.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
115
|
+
pytrilogy-0.0.3.60.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
|
|
116
|
+
pytrilogy-0.0.3.60.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
117
|
+
pytrilogy-0.0.3.60.dist-info/RECORD,,
|
trilogy/__init__.py
CHANGED
trilogy/authoring/__init__.py
CHANGED
|
@@ -43,14 +43,19 @@ from trilogy.core.models.core import (
|
|
|
43
43
|
MapType,
|
|
44
44
|
StructType,
|
|
45
45
|
)
|
|
46
|
-
from trilogy.core.models.datasource import Datasource, DatasourceMetadata
|
|
46
|
+
from trilogy.core.models.datasource import Address, Datasource, DatasourceMetadata
|
|
47
47
|
from trilogy.core.models.environment import Environment
|
|
48
48
|
from trilogy.core.statements.author import (
|
|
49
49
|
ConceptDeclarationStatement,
|
|
50
50
|
ConceptTransform,
|
|
51
|
+
CopyStatement,
|
|
52
|
+
Grain,
|
|
53
|
+
HasUUID,
|
|
54
|
+
ImportStatement,
|
|
51
55
|
MultiSelectStatement,
|
|
52
56
|
PersistStatement,
|
|
53
57
|
RawSQLStatement,
|
|
58
|
+
RowsetDerivationStatement,
|
|
54
59
|
SelectItem,
|
|
55
60
|
SelectStatement,
|
|
56
61
|
)
|
|
@@ -73,6 +78,8 @@ __all__ = [
|
|
|
73
78
|
"DataType",
|
|
74
79
|
"StructType",
|
|
75
80
|
"ListType",
|
|
81
|
+
"Grain",
|
|
82
|
+
"RowsetDerivationStatement",
|
|
76
83
|
"MapType",
|
|
77
84
|
"ListWrapper",
|
|
78
85
|
"FunctionType",
|
|
@@ -109,4 +116,8 @@ __all__ = [
|
|
|
109
116
|
"MultiSelectLineage",
|
|
110
117
|
"RowsetItem",
|
|
111
118
|
"FunctionCallWrapper",
|
|
119
|
+
"CopyStatement",
|
|
120
|
+
"HasUUID",
|
|
121
|
+
"ImportStatement",
|
|
122
|
+
"Address",
|
|
112
123
|
]
|
trilogy/core/constants.py
CHANGED
trilogy/core/enums.py
CHANGED
trilogy/core/models/execute.py
CHANGED
|
@@ -658,18 +658,21 @@ class QueryDatasource(BaseModel):
|
|
|
658
658
|
@classmethod
|
|
659
659
|
def validate_source_map(cls, v: dict, info: ValidationInfo):
|
|
660
660
|
values = info.data
|
|
661
|
+
hidden_concepts = values.get("hidden_concepts", set())
|
|
661
662
|
for key in ("input_concepts", "output_concepts"):
|
|
662
663
|
if not values.get(key):
|
|
663
664
|
continue
|
|
664
665
|
concept: BuildConcept
|
|
665
666
|
for concept in values[key]:
|
|
667
|
+
if concept.address in hidden_concepts:
|
|
668
|
+
continue
|
|
666
669
|
if (
|
|
667
670
|
concept.address not in v
|
|
668
671
|
and not any(x in v for x in concept.pseudonyms)
|
|
669
672
|
and CONFIG.validate_missing
|
|
670
673
|
):
|
|
671
674
|
raise SyntaxError(
|
|
672
|
-
f"On query datasource missing source map for {concept.address} on {key} with pseudonyms {concept.pseudonyms}, have {v}"
|
|
675
|
+
f"On query datasource from {values} missing source map entry (map: {v}) for {concept.address} on {key} with pseudonyms {concept.pseudonyms}, have {v}"
|
|
673
676
|
)
|
|
674
677
|
return v
|
|
675
678
|
|
trilogy/core/optimization.py
CHANGED
|
@@ -141,9 +141,7 @@ def is_direct_return_eligible(cte: CTE | UnionCTE) -> CTE | UnionCTE | None:
|
|
|
141
141
|
if not output_addresses.issubset(parent_output_addresses):
|
|
142
142
|
return None
|
|
143
143
|
if not direct_parent.grain == cte.grain:
|
|
144
|
-
logger.info("grain mismatch, cannot early exit")
|
|
145
|
-
logger.info(direct_parent.grain)
|
|
146
|
-
logger.info(cte.grain)
|
|
144
|
+
logger.info("[Direct Return] grain mismatch, cannot early exit")
|
|
147
145
|
return None
|
|
148
146
|
|
|
149
147
|
assert isinstance(cte, CTE)
|
|
@@ -236,6 +234,8 @@ def optimize_ctes(
|
|
|
236
234
|
complete = not actions_taken
|
|
237
235
|
loops += 1
|
|
238
236
|
input = reorder_ctes(filter_irrelevant_ctes(input, root_cte))
|
|
239
|
-
logger.info(
|
|
237
|
+
logger.info(
|
|
238
|
+
f"[Optimization] Finished checking for {type(rule).__name__} after {loops} loop(s)"
|
|
239
|
+
)
|
|
240
240
|
|
|
241
241
|
return reorder_ctes(filter_irrelevant_ctes(input, root_cte))
|
|
@@ -408,8 +408,9 @@ def generate_loop_completion(context: LoopContext, virtual) -> StrategyNode:
|
|
|
408
408
|
logger.info(
|
|
409
409
|
f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Conditions {context.conditions} were injected, checking if we need a group to restore grain"
|
|
410
410
|
)
|
|
411
|
+
|
|
411
412
|
result = GroupNode.check_if_required(
|
|
412
|
-
downstream_concepts=
|
|
413
|
+
downstream_concepts=output.usable_outputs,
|
|
413
414
|
parents=[output.resolve()],
|
|
414
415
|
environment=context.environment,
|
|
415
416
|
depth=context.depth,
|
|
@@ -420,7 +421,7 @@ def generate_loop_completion(context: LoopContext, virtual) -> StrategyNode:
|
|
|
420
421
|
)
|
|
421
422
|
return GroupNode(
|
|
422
423
|
output_concepts=context.original_mandatory,
|
|
423
|
-
input_concepts=
|
|
424
|
+
input_concepts=output.usable_outputs,
|
|
424
425
|
environment=context.environment,
|
|
425
426
|
parents=[output],
|
|
426
427
|
partial_concepts=output.partial_concepts,
|
|
@@ -504,7 +505,7 @@ def _search_concepts(
|
|
|
504
505
|
conditions=context.conditions,
|
|
505
506
|
accept_partial=accept_partial,
|
|
506
507
|
)
|
|
507
|
-
#
|
|
508
|
+
# assign
|
|
508
509
|
context.found = found_c
|
|
509
510
|
early_exit = check_for_early_exit(complete, partial, context, priority_concept)
|
|
510
511
|
if early_exit:
|
|
@@ -307,12 +307,18 @@ class RootNodeHandler:
|
|
|
307
307
|
def _resolve_root_concepts(
|
|
308
308
|
self, root_targets: List[BuildConcept]
|
|
309
309
|
) -> Optional[StrategyNode]:
|
|
310
|
-
|
|
310
|
+
synonym_node = self._try_synonym_resolution(root_targets)
|
|
311
|
+
if synonym_node:
|
|
312
|
+
logger.info(
|
|
313
|
+
f"{depth_to_prefix(self.ctx.depth)}{LOGGER_PREFIX} "
|
|
314
|
+
f"resolved root concepts through synonyms"
|
|
315
|
+
)
|
|
316
|
+
return synonym_node
|
|
311
317
|
expanded_node = self._try_merge_expansion(root_targets)
|
|
312
318
|
if expanded_node:
|
|
313
319
|
return expanded_node
|
|
314
320
|
|
|
315
|
-
return
|
|
321
|
+
return None
|
|
316
322
|
|
|
317
323
|
def _try_merge_expansion(
|
|
318
324
|
self, root_targets: List[BuildConcept]
|
|
@@ -367,7 +373,7 @@ class RootNodeHandler:
|
|
|
367
373
|
) -> Optional[StrategyNode]:
|
|
368
374
|
logger.info(
|
|
369
375
|
f"{depth_to_prefix(self.ctx.depth)}{LOGGER_PREFIX} "
|
|
370
|
-
f"Could not resolve root concepts, checking for synonyms"
|
|
376
|
+
f"Could not resolve root concepts, checking for synonyms for {root_targets}"
|
|
371
377
|
)
|
|
372
378
|
|
|
373
379
|
if not self.ctx.history.check_started(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
3
|
from trilogy.constants import logger
|
|
4
|
-
from trilogy.core.enums import FunctionClass, SourceType
|
|
4
|
+
from trilogy.core.enums import FunctionClass, FunctionType, SourceType
|
|
5
5
|
from trilogy.core.models.build import BuildConcept, BuildFunction, BuildWhereClause
|
|
6
6
|
from trilogy.core.models.build_environment import BuildEnvironment
|
|
7
7
|
from trilogy.core.processing.node_generators.common import (
|
|
@@ -47,13 +47,25 @@ def gen_basic_node(
|
|
|
47
47
|
logger.info(
|
|
48
48
|
f"{depth_prefix}{LOGGER_PREFIX} basic node for {concept} with lineage {concept.lineage} has parents {[x for x in parent_concepts]}"
|
|
49
49
|
)
|
|
50
|
-
|
|
50
|
+
synonyms: list[BuildConcept] = []
|
|
51
|
+
ignored_optional: set[str] = set()
|
|
52
|
+
assert isinstance(concept.lineage, BuildFunction)
|
|
53
|
+
if concept.lineage.operator == FunctionType.ATTR_ACCESS:
|
|
54
|
+
logger.info(
|
|
55
|
+
f"{depth_prefix}{LOGGER_PREFIX} checking for synonyms for attribute access"
|
|
56
|
+
)
|
|
57
|
+
for x in local_optional:
|
|
58
|
+
for z in x.pseudonyms:
|
|
59
|
+
s_concept = environment.alias_origin_lookup[z]
|
|
60
|
+
if is_equivalent_basic_function_lineage(concept, s_concept):
|
|
61
|
+
synonyms.append(s_concept)
|
|
62
|
+
ignored_optional.add(x.address)
|
|
51
63
|
equivalent_optional = [
|
|
52
64
|
x
|
|
53
65
|
for x in local_optional
|
|
54
66
|
if is_equivalent_basic_function_lineage(concept, x)
|
|
55
67
|
and x.address != concept.address
|
|
56
|
-
]
|
|
68
|
+
] + synonyms
|
|
57
69
|
|
|
58
70
|
if equivalent_optional:
|
|
59
71
|
logger.info(
|
|
@@ -66,6 +78,7 @@ def gen_basic_node(
|
|
|
66
78
|
for x in local_optional
|
|
67
79
|
if x not in equivalent_optional
|
|
68
80
|
and not any(x.address in y.pseudonyms for y in equivalent_optional)
|
|
81
|
+
and x.address not in ignored_optional
|
|
69
82
|
]
|
|
70
83
|
all_parents: list[BuildConcept] = unique(
|
|
71
84
|
parent_concepts + non_equivalent_optional, "address"
|
|
@@ -73,7 +86,7 @@ def gen_basic_node(
|
|
|
73
86
|
logger.info(
|
|
74
87
|
f"{depth_prefix}{LOGGER_PREFIX} Fetching parents {[x.address for x in all_parents]}"
|
|
75
88
|
)
|
|
76
|
-
parent_node: StrategyNode = source_concepts(
|
|
89
|
+
parent_node: StrategyNode | None = source_concepts(
|
|
77
90
|
mandatory_list=all_parents,
|
|
78
91
|
environment=environment,
|
|
79
92
|
g=g,
|
|
@@ -92,14 +105,19 @@ def gen_basic_node(
|
|
|
92
105
|
parent_node.add_output_concept(concept)
|
|
93
106
|
for x in equivalent_optional:
|
|
94
107
|
parent_node.add_output_concept(x)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
[
|
|
98
|
-
x
|
|
99
|
-
for x in parent_node.output_concepts
|
|
100
|
-
if x.address not in [concept] + local_optional
|
|
101
|
-
]
|
|
108
|
+
targets = [concept] + local_optional
|
|
109
|
+
logger.info(
|
|
110
|
+
f"{depth_prefix}{LOGGER_PREFIX} Returning basic select for {concept}: output {[x.address for x in parent_node.output_concepts]}"
|
|
102
111
|
)
|
|
112
|
+
should_hide = [
|
|
113
|
+
x
|
|
114
|
+
for x in parent_node.output_concepts
|
|
115
|
+
if (
|
|
116
|
+
x.address not in targets
|
|
117
|
+
and not any(x.address in y.pseudonyms for y in targets)
|
|
118
|
+
)
|
|
119
|
+
]
|
|
120
|
+
parent_node.hide_output_concepts(should_hide)
|
|
103
121
|
|
|
104
122
|
logger.info(
|
|
105
123
|
f"{depth_prefix}{LOGGER_PREFIX} Returning basic select for {concept}: output {[x.address for x in parent_node.output_concepts]}"
|
|
@@ -88,7 +88,10 @@ def determine_induced_minimal_nodes(
|
|
|
88
88
|
for node in G.nodes:
|
|
89
89
|
if concepts.get(node):
|
|
90
90
|
lookup: BuildConcept = concepts[node]
|
|
91
|
-
|
|
91
|
+
# inclusion of aggregates can create ambiguous node relation chains
|
|
92
|
+
# there may be a better way to handle this
|
|
93
|
+
# can be revisited if we need to connect a derived synonym based on an aggregate
|
|
94
|
+
if lookup.derivation in (Derivation.CONSTANT, Derivation.AGGREGATE):
|
|
92
95
|
nodes_to_remove.append(node)
|
|
93
96
|
# purge a node if we're already looking for all it's parents
|
|
94
97
|
if filter_downstream and lookup.derivation not in (Derivation.ROOT,):
|
|
@@ -112,6 +115,7 @@ def determine_induced_minimal_nodes(
|
|
|
112
115
|
return None
|
|
113
116
|
H.remove_nodes_from(list(x for x in H.nodes if x not in paths))
|
|
114
117
|
sG: nx.Graph = ax.steinertree.steiner_tree(H, nodelist).copy()
|
|
118
|
+
logger.debug("Steiner tree found for nodes %s", nodelist)
|
|
115
119
|
final: nx.DiGraph = nx.subgraph(G, sG.nodes).copy()
|
|
116
120
|
|
|
117
121
|
for edge in G.edges:
|
|
@@ -143,7 +147,7 @@ def determine_induced_minimal_nodes(
|
|
|
143
147
|
if not all([node in final.nodes for node in nodelist]):
|
|
144
148
|
missing = [node for node in nodelist if node not in final.nodes]
|
|
145
149
|
logger.debug(
|
|
146
|
-
f"Skipping graph for {nodelist} as missing nodes {missing} from {final.nodes}"
|
|
150
|
+
f"Skipping graph for initial list {nodelist} as missing nodes {missing} from final graph {final.nodes}"
|
|
147
151
|
)
|
|
148
152
|
return None
|
|
149
153
|
logger.debug(f"Found final graph {final.nodes}")
|
|
@@ -228,11 +232,14 @@ def resolve_weak_components(
|
|
|
228
232
|
# to ensure there are not ambiguous discovery paths
|
|
229
233
|
# (if we did not care about raising ambiguity errors, we could just use the first one)
|
|
230
234
|
count = 0
|
|
231
|
-
node_list =
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
235
|
+
node_list = sorted(
|
|
236
|
+
[
|
|
237
|
+
concept_to_node(c.with_default_grain())
|
|
238
|
+
for c in all_concepts
|
|
239
|
+
if "__preql_internal" not in c.address
|
|
240
|
+
]
|
|
241
|
+
)
|
|
242
|
+
logger.debug(f"Resolving weak components for {node_list} in {search_graph.nodes}")
|
|
236
243
|
synonyms: set[str] = set()
|
|
237
244
|
for x in all_concepts:
|
|
238
245
|
synonyms = synonyms.union(x.pseudonyms)
|
|
@@ -354,7 +361,7 @@ def subgraphs_to_merge_node(
|
|
|
354
361
|
parents.append(parent)
|
|
355
362
|
input_c = []
|
|
356
363
|
for x in parents:
|
|
357
|
-
for y in x.
|
|
364
|
+
for y in x.usable_outputs:
|
|
358
365
|
input_c.append(y)
|
|
359
366
|
if len(parents) == 1 and enable_early_exit:
|
|
360
367
|
logger.info(
|
|
@@ -392,6 +399,7 @@ def gen_merge_node(
|
|
|
392
399
|
)
|
|
393
400
|
else:
|
|
394
401
|
all_search_concepts = all_concepts
|
|
402
|
+
all_search_concepts = sorted(all_search_concepts, key=lambda x: x.address)
|
|
395
403
|
for filter_downstream in [True, False]:
|
|
396
404
|
weak_resolve = resolve_weak_components(
|
|
397
405
|
all_search_concepts,
|
|
@@ -29,7 +29,7 @@ def gen_synonym_node(
|
|
|
29
29
|
conditions: BuildWhereClause | None = None,
|
|
30
30
|
accept_partial: bool = False,
|
|
31
31
|
) -> StrategyNode | None:
|
|
32
|
-
local_prefix = f"
|
|
32
|
+
local_prefix = f"{padding(depth)}[GEN_SYNONYM_NODE]"
|
|
33
33
|
base_fingerprint = tuple([x.address for x in all_concepts])
|
|
34
34
|
synonyms = defaultdict(list)
|
|
35
35
|
synonym_count = 0
|
|
@@ -64,5 +64,6 @@ def gen_synonym_node(
|
|
|
64
64
|
)
|
|
65
65
|
if attempt:
|
|
66
66
|
logger.info(f"{local_prefix} found inputs with {combo}")
|
|
67
|
+
print(attempt.output_concepts)
|
|
67
68
|
return attempt
|
|
68
69
|
return None
|
|
@@ -20,16 +20,22 @@ def gen_unnest_node(
|
|
|
20
20
|
conditions: BuildWhereClause | None = None,
|
|
21
21
|
) -> StrategyNode | None:
|
|
22
22
|
arguments = []
|
|
23
|
+
depth_prefix = "\t" * depth
|
|
23
24
|
if isinstance(concept.lineage, BuildFunction):
|
|
24
25
|
arguments = concept.lineage.concept_arguments
|
|
25
26
|
|
|
26
27
|
equivalent_optional = [x for x in local_optional if x.lineage == concept.lineage]
|
|
28
|
+
|
|
27
29
|
non_equivalent_optional = [
|
|
28
30
|
x for x in local_optional if x not in equivalent_optional
|
|
29
31
|
]
|
|
32
|
+
all_parents = arguments + non_equivalent_optional
|
|
33
|
+
logger.info(
|
|
34
|
+
f"{depth_prefix}{LOGGER_PREFIX} unnest node for {concept} with lineage {concept.lineage} has parents {all_parents} and equivalent optional {equivalent_optional}"
|
|
35
|
+
)
|
|
30
36
|
if arguments or local_optional:
|
|
31
37
|
parent = source_concepts(
|
|
32
|
-
mandatory_list=
|
|
38
|
+
mandatory_list=all_parents,
|
|
33
39
|
environment=environment,
|
|
34
40
|
g=g,
|
|
35
41
|
depth=depth + 1,
|
|
@@ -311,19 +311,6 @@ class StrategyNode:
|
|
|
311
311
|
self.rebuild_cache()
|
|
312
312
|
return self
|
|
313
313
|
|
|
314
|
-
def remove_output_concepts(
|
|
315
|
-
self, concepts: List[BuildConcept], rebuild: bool = True
|
|
316
|
-
):
|
|
317
|
-
for x in concepts:
|
|
318
|
-
self.hidden_concepts.add(x.address)
|
|
319
|
-
addresses = [x.address for x in concepts]
|
|
320
|
-
self.output_concepts = [
|
|
321
|
-
x for x in self.output_concepts if x.address not in addresses
|
|
322
|
-
]
|
|
323
|
-
if rebuild:
|
|
324
|
-
self.rebuild_cache()
|
|
325
|
-
return self
|
|
326
|
-
|
|
327
314
|
@property
|
|
328
315
|
def usable_outputs(self) -> list[BuildConcept]:
|
|
329
316
|
return [
|
|
@@ -105,7 +105,7 @@ class GroupNode(StrategyNode):
|
|
|
105
105
|
if comp_grain.issubset(target_grain):
|
|
106
106
|
|
|
107
107
|
logger.info(
|
|
108
|
-
f"{padding}{LOGGER_PREFIX} Group requirement check: {comp_grain}, {target_grain}, is subset, no
|
|
108
|
+
f"{padding}{LOGGER_PREFIX} Group requirement check: {comp_grain}, {target_grain}, grain is subset of target, no group node required"
|
|
109
109
|
)
|
|
110
110
|
return GroupRequiredResponse(target_grain, comp_grain, False)
|
|
111
111
|
# find out what extra is in the comp grain vs target grain
|
|
@@ -409,7 +409,7 @@ def get_node_joins(
|
|
|
409
409
|
|
|
410
410
|
|
|
411
411
|
def get_disconnected_components(
|
|
412
|
-
concept_map: Dict[str, Set[BuildConcept]]
|
|
412
|
+
concept_map: Dict[str, Set[BuildConcept]],
|
|
413
413
|
) -> Tuple[int, List]:
|
|
414
414
|
"""Find if any of the datasources are not linked"""
|
|
415
415
|
import networkx as nx
|
|
@@ -608,8 +608,24 @@ def sort_select_output_processed(
|
|
|
608
608
|
mapping = {x.address: x for x in cte.output_columns}
|
|
609
609
|
|
|
610
610
|
new_output: list[BuildConcept] = []
|
|
611
|
-
for x in
|
|
612
|
-
|
|
611
|
+
for x in query.output_columns:
|
|
612
|
+
if x.address in mapping:
|
|
613
|
+
new_output.append(mapping[x.address])
|
|
614
|
+
for oc in cte.output_columns:
|
|
615
|
+
if x.address in oc.pseudonyms:
|
|
616
|
+
# create a wrapper BuildConcept to render the pseudonym under the original name
|
|
617
|
+
new_output.append(
|
|
618
|
+
BuildConcept(
|
|
619
|
+
name=x.name,
|
|
620
|
+
namespace=x.namespace,
|
|
621
|
+
pseudonyms={oc.address},
|
|
622
|
+
datatype=oc.datatype,
|
|
623
|
+
purpose=oc.purpose,
|
|
624
|
+
grain=oc.grain,
|
|
625
|
+
build_is_aggregate=oc.build_is_aggregate,
|
|
626
|
+
)
|
|
627
|
+
)
|
|
628
|
+
break
|
|
613
629
|
|
|
614
630
|
for oc in cte.output_columns:
|
|
615
631
|
# add hidden back
|
|
@@ -637,17 +653,28 @@ def sort_select_output(
|
|
|
637
653
|
if isinstance(query, ProcessedQuery):
|
|
638
654
|
return sort_select_output_processed(cte, query)
|
|
639
655
|
|
|
640
|
-
output_addresses = [
|
|
641
|
-
c.address
|
|
642
|
-
for c in query.output_components
|
|
643
|
-
# if c.address not in query.hidden_components
|
|
644
|
-
]
|
|
645
|
-
|
|
646
656
|
mapping = {x.address: x for x in cte.output_columns}
|
|
647
657
|
|
|
648
658
|
new_output: list[BuildConcept] = []
|
|
649
|
-
for x in
|
|
650
|
-
|
|
659
|
+
for x in query.output_components:
|
|
660
|
+
if x.address in mapping:
|
|
661
|
+
new_output.append(mapping[x.address])
|
|
662
|
+
else:
|
|
663
|
+
for oc in cte.output_columns:
|
|
664
|
+
if x.address in oc.pseudonyms:
|
|
665
|
+
# create a wrapper BuildConcept to render the pseudonym under the original name
|
|
666
|
+
new_output.append(
|
|
667
|
+
BuildConcept(
|
|
668
|
+
name=x.name,
|
|
669
|
+
namespace=x.namespace,
|
|
670
|
+
pseudonyms={oc.address},
|
|
671
|
+
datatype=oc.datatype,
|
|
672
|
+
purpose=oc.purpose,
|
|
673
|
+
grain=oc.grain,
|
|
674
|
+
build_is_aggregate=oc.build_is_aggregate,
|
|
675
|
+
)
|
|
676
|
+
)
|
|
677
|
+
break
|
|
651
678
|
cte.output_columns = new_output
|
|
652
679
|
cte.hidden_concepts = set(
|
|
653
680
|
[
|
trilogy/core/query_processor.py
CHANGED
|
@@ -432,7 +432,7 @@ def get_query_node(
|
|
|
432
432
|
)
|
|
433
433
|
ds = SelectNode(
|
|
434
434
|
output_concepts=build_statement.output_components,
|
|
435
|
-
input_concepts=ds.
|
|
435
|
+
input_concepts=ds.usable_outputs,
|
|
436
436
|
parents=[ds],
|
|
437
437
|
environment=ds.environment,
|
|
438
438
|
partial_concepts=ds.partial_concepts,
|
|
@@ -553,11 +553,11 @@ def process_query(
|
|
|
553
553
|
root_cte.hidden_concepts = statement.hidden_components
|
|
554
554
|
|
|
555
555
|
final_ctes = optimize_ctes(deduped_ctes, root_cte, statement)
|
|
556
|
-
|
|
556
|
+
|
|
557
557
|
return ProcessedQuery(
|
|
558
558
|
order_by=root_cte.order_by,
|
|
559
559
|
limit=statement.limit,
|
|
560
|
-
output_columns=
|
|
560
|
+
output_columns=statement.output_components,
|
|
561
561
|
ctes=final_ctes,
|
|
562
562
|
base=root_cte,
|
|
563
563
|
hidden_columns=set([x for x in statement.hidden_components]),
|
|
@@ -5,7 +5,7 @@ from typing import Annotated, List, Optional, Union
|
|
|
5
5
|
from pydantic import BaseModel, Field, computed_field, field_validator
|
|
6
6
|
from pydantic.functional_validators import PlainValidator
|
|
7
7
|
|
|
8
|
-
from trilogy.constants import CONFIG
|
|
8
|
+
from trilogy.constants import CONFIG, DEFAULT_NAMESPACE
|
|
9
9
|
from trilogy.core.enums import (
|
|
10
10
|
ConceptSource,
|
|
11
11
|
FunctionClass,
|
|
@@ -281,7 +281,11 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
|
|
|
281
281
|
# if the concept is a locally derived concept, it cannot ever be partial
|
|
282
282
|
# but if it's a concept pulled in from upstream and we have a where clause, it should be partial
|
|
283
283
|
ColumnAssignment(
|
|
284
|
-
alias=
|
|
284
|
+
alias=(
|
|
285
|
+
c.address.replace(".", "_")
|
|
286
|
+
if c.namespace != DEFAULT_NAMESPACE
|
|
287
|
+
else c.name
|
|
288
|
+
),
|
|
285
289
|
concept=environment.concepts[c.address].reference,
|
|
286
290
|
modifiers=modifiers if c.address not in self.locally_derived else [],
|
|
287
291
|
)
|
|
@@ -3,6 +3,7 @@ from typing import Annotated, List, Optional, Union
|
|
|
3
3
|
from pydantic import BaseModel, Field
|
|
4
4
|
from pydantic.functional_validators import PlainValidator
|
|
5
5
|
|
|
6
|
+
from trilogy.core.models.author import ConceptRef
|
|
6
7
|
from trilogy.core.models.build import (
|
|
7
8
|
BuildConcept,
|
|
8
9
|
BuildDatasource,
|
|
@@ -14,7 +15,7 @@ from trilogy.core.statements.common import CopyQueryMixin, PersistQueryMixin
|
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class ProcessedQuery(BaseModel):
|
|
17
|
-
output_columns: List[
|
|
18
|
+
output_columns: List[ConceptRef]
|
|
18
19
|
ctes: List[CTE | UnionCTE]
|
|
19
20
|
base: CTE | UnionCTE
|
|
20
21
|
hidden_columns: set[str] = Field(default_factory=set)
|
|
@@ -38,5 +39,5 @@ class ProcessedRawSQLStatement(BaseModel):
|
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
class ProcessedShowStatement(BaseModel):
|
|
41
|
-
output_columns: List[
|
|
42
|
+
output_columns: List[ConceptRef]
|
|
42
43
|
output_values: List[Union[BuildConcept, BuildDatasource, ProcessedQuery]]
|
trilogy/dialect/base.py
CHANGED
|
@@ -10,6 +10,7 @@ from trilogy.constants import (
|
|
|
10
10
|
Rendering,
|
|
11
11
|
logger,
|
|
12
12
|
)
|
|
13
|
+
from trilogy.core.constants import UNNEST_NAME
|
|
13
14
|
from trilogy.core.enums import (
|
|
14
15
|
DatePart,
|
|
15
16
|
FunctionType,
|
|
@@ -33,7 +34,6 @@ from trilogy.core.models.build import (
|
|
|
33
34
|
BuildRowsetItem,
|
|
34
35
|
BuildSubselectComparison,
|
|
35
36
|
BuildWindowItem,
|
|
36
|
-
Factory,
|
|
37
37
|
)
|
|
38
38
|
from trilogy.core.models.core import (
|
|
39
39
|
DataType,
|
|
@@ -757,6 +757,16 @@ class BaseDialect:
|
|
|
757
757
|
f"{self.QUOTE_CHARACTER}{c.safe_address}{self.QUOTE_CHARACTER}"
|
|
758
758
|
for c in cte.join_derived_concepts
|
|
759
759
|
]
|
|
760
|
+
elif self.UNNEST_MODE == UnnestMode.CROSS_JOIN_UNNEST:
|
|
761
|
+
select_columns = [
|
|
762
|
+
self.render_concept_sql(c, cte)
|
|
763
|
+
for c in cte.output_columns
|
|
764
|
+
if c.address not in [y.address for y in cte.join_derived_concepts]
|
|
765
|
+
and c.address not in cte.hidden_concepts
|
|
766
|
+
] + [
|
|
767
|
+
f"{UNNEST_NAME} as {self.QUOTE_CHARACTER}{c.safe_address}{self.QUOTE_CHARACTER}"
|
|
768
|
+
for c in cte.join_derived_concepts
|
|
769
|
+
]
|
|
760
770
|
else:
|
|
761
771
|
# otherwse, assume we are unnesting directly in the select
|
|
762
772
|
select_columns = [
|
|
@@ -771,11 +781,18 @@ class BaseDialect:
|
|
|
771
781
|
if len(cte.joins) > 0:
|
|
772
782
|
if cte.join_derived_concepts and self.UNNEST_MODE in (
|
|
773
783
|
UnnestMode.CROSS_JOIN_ALIAS,
|
|
784
|
+
# UnnestMode.CROSS_JOIN_UNNEST,
|
|
774
785
|
UnnestMode.CROSS_JOIN,
|
|
775
786
|
UnnestMode.CROSS_APPLY,
|
|
776
787
|
):
|
|
777
788
|
|
|
778
789
|
source = f"{render_unnest(self.UNNEST_MODE, self.QUOTE_CHARACTER, cte.join_derived_concepts[0], self.render_expr, cte)}"
|
|
790
|
+
elif (
|
|
791
|
+
cte.join_derived_concepts
|
|
792
|
+
and self.UNNEST_MODE == UnnestMode.CROSS_JOIN_UNNEST
|
|
793
|
+
):
|
|
794
|
+
source = f"{self.render_expr(cte.join_derived_concepts[0], cte)} as {self.QUOTE_CHARACTER}{UNNEST_NAME}{self.QUOTE_CHARACTER}"
|
|
795
|
+
|
|
779
796
|
elif (
|
|
780
797
|
cte.join_derived_concepts
|
|
781
798
|
and self.UNNEST_MODE == UnnestMode.SNOWFLAKE
|
|
@@ -904,7 +921,6 @@ class BaseDialect:
|
|
|
904
921
|
| ProcessedRawSQLStatement
|
|
905
922
|
| ProcessedCopyStatement
|
|
906
923
|
] = []
|
|
907
|
-
factory = Factory(environment=environment)
|
|
908
924
|
for statement in statements:
|
|
909
925
|
if isinstance(statement, PersistStatement):
|
|
910
926
|
if hooks:
|
|
@@ -939,11 +955,9 @@ class BaseDialect:
|
|
|
939
955
|
output.append(
|
|
940
956
|
ProcessedShowStatement(
|
|
941
957
|
output_columns=[
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
]
|
|
946
|
-
)
|
|
958
|
+
environment.concepts[
|
|
959
|
+
DEFAULT_CONCEPTS["query_text"].address
|
|
960
|
+
].reference
|
|
947
961
|
],
|
|
948
962
|
output_values=[
|
|
949
963
|
process_query(
|
|
@@ -984,29 +998,6 @@ class BaseDialect:
|
|
|
984
998
|
return ";\n".join([str(x) for x in query.output_values])
|
|
985
999
|
elif isinstance(query, ProcessedRawSQLStatement):
|
|
986
1000
|
return query.text
|
|
987
|
-
select_columns: Dict[str, str] = {}
|
|
988
|
-
cte_output_map = {}
|
|
989
|
-
selected = set()
|
|
990
|
-
output_addresses = [
|
|
991
|
-
c.address
|
|
992
|
-
for c in query.output_columns
|
|
993
|
-
if c.address not in query.hidden_columns
|
|
994
|
-
]
|
|
995
|
-
|
|
996
|
-
for c in query.base.output_columns:
|
|
997
|
-
if c.address not in selected:
|
|
998
|
-
select_columns[c.address] = (
|
|
999
|
-
f"{query.base.name}.{safe_quote(c.safe_address, self.QUOTE_CHARACTER)}"
|
|
1000
|
-
)
|
|
1001
|
-
cte_output_map[c.address] = query.base
|
|
1002
|
-
if c.address not in query.hidden_columns:
|
|
1003
|
-
selected.add(c.address)
|
|
1004
|
-
if not all([x in selected for x in output_addresses]):
|
|
1005
|
-
missing = [x for x in output_addresses if x not in selected]
|
|
1006
|
-
raise ValueError(
|
|
1007
|
-
f"Did not get all output addresses in select - missing: {missing}, have"
|
|
1008
|
-
f" {selected}"
|
|
1009
|
-
)
|
|
1010
1001
|
|
|
1011
1002
|
recursive = any(isinstance(x, RecursiveCTE) for x in query.ctes)
|
|
1012
1003
|
|
trilogy/dialect/bigquery.py
CHANGED
|
@@ -22,7 +22,7 @@ FUNCTION_MAP = {
|
|
|
22
22
|
FunctionType.MINUTE: lambda x: f"EXTRACT(MINUTE from {x[0]})",
|
|
23
23
|
FunctionType.SECOND: lambda x: f"EXTRACT(SECOND from {x[0]})",
|
|
24
24
|
FunctionType.HOUR: lambda x: f"EXTRACT(HOUR from {x[0]})",
|
|
25
|
-
FunctionType.DAY_OF_WEEK: lambda x: f"EXTRACT(DAYOFWEEK from {x[0]})",
|
|
25
|
+
FunctionType.DAY_OF_WEEK: lambda x: f"EXTRACT(DAYOFWEEK from {x[0]})-1", # BigQuery's DAYOFWEEK returns 1 for Sunday
|
|
26
26
|
FunctionType.DAY: lambda x: f"EXTRACT(DAY from {x[0]})",
|
|
27
27
|
FunctionType.YEAR: lambda x: f"EXTRACT(YEAR from {x[0]})",
|
|
28
28
|
FunctionType.MONTH: lambda x: f"EXTRACT(MONTH from {x[0]})",
|
|
@@ -97,5 +97,5 @@ class BigqueryDialect(BaseDialect):
|
|
|
97
97
|
}
|
|
98
98
|
QUOTE_CHARACTER = "`"
|
|
99
99
|
SQL_TEMPLATE = BQ_SQL_TEMPLATE
|
|
100
|
-
UNNEST_MODE = UnnestMode.
|
|
100
|
+
UNNEST_MODE = UnnestMode.CROSS_JOIN_UNNEST
|
|
101
101
|
DATATYPE_MAP = DATATYPE_MAP
|
trilogy/dialect/common.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Callable
|
|
2
2
|
|
|
3
|
+
from trilogy.core.constants import UNNEST_NAME
|
|
3
4
|
from trilogy.core.enums import Modifier, UnnestMode
|
|
4
5
|
from trilogy.core.models.build import (
|
|
5
6
|
BuildComparison,
|
|
@@ -34,11 +35,14 @@ def render_unnest(
|
|
|
34
35
|
cte: CTE,
|
|
35
36
|
):
|
|
36
37
|
if not isinstance(concept, (BuildConcept, BuildParamaterizedConceptReference)):
|
|
37
|
-
|
|
38
|
+
print(type(concept))
|
|
39
|
+
address = UNNEST_NAME
|
|
38
40
|
else:
|
|
39
41
|
address = concept.safe_address
|
|
40
42
|
if unnest_mode == UnnestMode.CROSS_JOIN:
|
|
41
43
|
return f"{render_func(concept, cte)} as {quote_character}{address}{quote_character}"
|
|
44
|
+
elif unnest_mode == UnnestMode.CROSS_JOIN_UNNEST:
|
|
45
|
+
return f"unnest({render_func(concept, cte)}) as {quote_character}{address}{quote_character}"
|
|
42
46
|
elif unnest_mode == UnnestMode.CROSS_JOIN_ALIAS:
|
|
43
47
|
return f"{render_func(concept, cte)} as unnest_wrapper ({quote_character}{address}{quote_character})"
|
|
44
48
|
elif unnest_mode == UnnestMode.SNOWFLAKE:
|
|
@@ -95,9 +99,11 @@ def render_join(
|
|
|
95
99
|
return None
|
|
96
100
|
if not cte:
|
|
97
101
|
raise ValueError("must provide a cte to build an unnest joins")
|
|
98
|
-
if unnest_mode
|
|
99
|
-
|
|
100
|
-
|
|
102
|
+
if unnest_mode in (
|
|
103
|
+
UnnestMode.CROSS_JOIN,
|
|
104
|
+
UnnestMode.CROSS_JOIN_UNNEST,
|
|
105
|
+
UnnestMode.CROSS_JOIN_ALIAS,
|
|
106
|
+
):
|
|
101
107
|
return f"CROSS JOIN {render_unnest(unnest_mode, quote_character, join.object_to_unnest, render_expr_func, cte)}"
|
|
102
108
|
if unnest_mode == UnnestMode.SNOWFLAKE:
|
|
103
109
|
return f"LEFT JOIN LATERAL {render_unnest(unnest_mode, quote_character, join.object_to_unnest, render_expr_func, cte)}"
|
trilogy/executor.py
CHANGED
|
@@ -8,8 +8,8 @@ from sqlalchemy.engine import CursorResult
|
|
|
8
8
|
|
|
9
9
|
from trilogy.constants import Rendering, logger
|
|
10
10
|
from trilogy.core.enums import FunctionType, Granularity, IOType
|
|
11
|
-
from trilogy.core.models.author import Concept, Function
|
|
12
|
-
from trilogy.core.models.build import
|
|
11
|
+
from trilogy.core.models.author import Concept, ConceptRef, Function
|
|
12
|
+
from trilogy.core.models.build import BuildFunction
|
|
13
13
|
from trilogy.core.models.core import ListWrapper, MapWrapper
|
|
14
14
|
from trilogy.core.models.datasource import Datasource
|
|
15
15
|
from trilogy.core.models.environment import Environment
|
|
@@ -61,7 +61,7 @@ class MockResult:
|
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
def generate_result_set(
|
|
64
|
-
columns: List[
|
|
64
|
+
columns: List[ConceptRef], output_data: list[Any]
|
|
65
65
|
) -> MockResult:
|
|
66
66
|
names = [x.address.replace(".", "_") for x in columns]
|
|
67
67
|
return MockResult(
|
|
@@ -90,7 +90,16 @@ class Executor(object):
|
|
|
90
90
|
if self.dialect == Dialects.DATAFRAME:
|
|
91
91
|
self.engine.setup(self.environment, self.connection)
|
|
92
92
|
|
|
93
|
-
def execute_statement(
|
|
93
|
+
def execute_statement(
|
|
94
|
+
self,
|
|
95
|
+
statement: (
|
|
96
|
+
ProcessedQuery
|
|
97
|
+
| ProcessedCopyStatement
|
|
98
|
+
| ProcessedRawSQLStatement
|
|
99
|
+
| ProcessedQueryPersist
|
|
100
|
+
| ProcessedShowStatement
|
|
101
|
+
),
|
|
102
|
+
) -> Optional[CursorResult]:
|
|
94
103
|
if not isinstance(
|
|
95
104
|
statement,
|
|
96
105
|
(
|
trilogy/parsing/common.py
CHANGED
|
@@ -38,6 +38,7 @@ from trilogy.core.models.author import (
|
|
|
38
38
|
Parenthetical,
|
|
39
39
|
RowsetItem,
|
|
40
40
|
RowsetLineage,
|
|
41
|
+
TraitDataType,
|
|
41
42
|
UndefinedConcept,
|
|
42
43
|
WhereClause,
|
|
43
44
|
WindowItem,
|
|
@@ -608,6 +609,9 @@ def window_item_to_concept(
|
|
|
608
609
|
and set([x.address for x in item.expr.by]) == keys
|
|
609
610
|
):
|
|
610
611
|
continue
|
|
612
|
+
elif isinstance(item.expr, AggregateWrapper):
|
|
613
|
+
|
|
614
|
+
grain_components += item.expr.by
|
|
611
615
|
else:
|
|
612
616
|
grain_components += item.concept_arguments
|
|
613
617
|
else:
|
|
@@ -617,19 +621,20 @@ def window_item_to_concept(
|
|
|
617
621
|
modifiers = get_upstream_modifiers(bcontent.concept_arguments, environment)
|
|
618
622
|
datatype = parent.content.datatype
|
|
619
623
|
if parent.type in (
|
|
620
|
-
WindowType.RANK,
|
|
624
|
+
# WindowType.RANK,
|
|
621
625
|
WindowType.ROW_NUMBER,
|
|
622
626
|
WindowType.COUNT,
|
|
623
627
|
WindowType.COUNT_DISTINCT,
|
|
624
628
|
):
|
|
625
629
|
datatype = DataType.INTEGER
|
|
630
|
+
if parent.type == WindowType.RANK:
|
|
631
|
+
datatype = TraitDataType(type=DataType.INTEGER, traits=["rank"])
|
|
626
632
|
return Concept(
|
|
627
633
|
name=name,
|
|
628
634
|
datatype=datatype,
|
|
629
635
|
purpose=local_purpose,
|
|
630
636
|
lineage=parent,
|
|
631
637
|
metadata=fmetadata,
|
|
632
|
-
# filters are implicitly at the grain of the base item
|
|
633
638
|
grain=final_grain,
|
|
634
639
|
namespace=namespace,
|
|
635
640
|
keys=keys,
|
trilogy/parsing/parse_engine.py
CHANGED
|
@@ -667,8 +667,14 @@ class ParseToObjects(Transformer):
|
|
|
667
667
|
environment=self.environment,
|
|
668
668
|
metadata=metadata,
|
|
669
669
|
)
|
|
670
|
-
|
|
671
|
-
|
|
670
|
+
# let constant purposes exist to support round-tripping
|
|
671
|
+
# as a build concept may end up with a constant based on constant inlining happening recursively
|
|
672
|
+
if (
|
|
673
|
+
purpose
|
|
674
|
+
and purpose != Purpose.AUTO
|
|
675
|
+
and concept.purpose != purpose
|
|
676
|
+
and purpose != Purpose.CONSTANT
|
|
677
|
+
):
|
|
672
678
|
raise SyntaxError(
|
|
673
679
|
f'Concept {name} purpose {concept.purpose} does not match declared purpose {purpose}. Suggest defaulting to "auto"'
|
|
674
680
|
)
|
trilogy/parsing/trilogy.lark
CHANGED
trilogy/std/date.preql
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|