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.

Files changed (33) hide show
  1. {pytrilogy-0.0.3.56.dist-info → pytrilogy-0.0.3.60.dist-info}/METADATA +1 -1
  2. {pytrilogy-0.0.3.56.dist-info → pytrilogy-0.0.3.60.dist-info}/RECORD +33 -32
  3. {pytrilogy-0.0.3.56.dist-info → pytrilogy-0.0.3.60.dist-info}/WHEEL +1 -1
  4. trilogy/__init__.py +1 -1
  5. trilogy/authoring/__init__.py +12 -1
  6. trilogy/core/constants.py +1 -0
  7. trilogy/core/enums.py +1 -0
  8. trilogy/core/models/execute.py +4 -1
  9. trilogy/core/optimization.py +4 -4
  10. trilogy/core/processing/concept_strategies_v3.py +4 -3
  11. trilogy/core/processing/discovery_node_factory.py +9 -3
  12. trilogy/core/processing/node_generators/basic_node.py +29 -11
  13. trilogy/core/processing/node_generators/node_merge_node.py +16 -8
  14. trilogy/core/processing/node_generators/synonym_node.py +2 -1
  15. trilogy/core/processing/node_generators/unnest_node.py +7 -1
  16. trilogy/core/processing/nodes/base_node.py +0 -13
  17. trilogy/core/processing/nodes/group_node.py +1 -1
  18. trilogy/core/processing/utility.py +38 -11
  19. trilogy/core/query_processor.py +3 -3
  20. trilogy/core/statements/author.py +6 -2
  21. trilogy/core/statements/execute.py +3 -2
  22. trilogy/dialect/base.py +21 -30
  23. trilogy/dialect/bigquery.py +2 -2
  24. trilogy/dialect/common.py +10 -4
  25. trilogy/executor.py +13 -4
  26. trilogy/parsing/common.py +7 -2
  27. trilogy/parsing/parse_engine.py +8 -2
  28. trilogy/parsing/trilogy.lark +1 -1
  29. trilogy/std/date.preql +4 -1
  30. trilogy/std/ranking.preql +6 -0
  31. {pytrilogy-0.0.3.56.dist-info → pytrilogy-0.0.3.60.dist-info}/entry_points.txt +0 -0
  32. {pytrilogy-0.0.3.56.dist-info → pytrilogy-0.0.3.60.dist-info}/licenses/LICENSE.md +0 -0
  33. {pytrilogy-0.0.3.56.dist-info → pytrilogy-0.0.3.60.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.56
3
+ Version: 0.0.3.60
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,17 +1,17 @@
1
- pytrilogy-0.0.3.56.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
- trilogy/__init__.py,sha256=E6tZFZr367OLf8N5bIFQ-PZJTvXpcgVF1NGc8rJZXsk,303
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=GwNhP9UW4565dxnpHbw-VWNE2lX8uroQJQtSpC_j2pI,16298
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=v9PRuZs4fTnxhpXAnwTxCDwlLasUax6g2FONidcujR4,2369
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=7XaCpZn5mQmjTobbeBn56SzPWq9eMNDfzfsRU-fP0VE,171
14
- trilogy/core/enums.py,sha256=9thKx6u-Z3vzT3iGBBUtHsHCXaU-8L4IaqGJp1G7It0,7737
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=ChIAv0kRmw9RKyDGDCdSdbIN5fJGMkIlE6eVfTFsxg4,8867
23
- trilogy/core/query_processor.py,sha256=jSS1xZFDqBuI0sZBbuYAAuuVGwas7W-mV_v5oFZJFpA,20275
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=_JC93S5tpCQM9jpgmmbd6wkLMEfPvaMZwWZBVcgehlI,42931
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=COK6rpKwhXxfawKhliz6DryqZa7FSAXQ6eD1eMMq1JI,22005
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=CFQPaqsJPqe0ZGnU1a81R1ZjlRNihbiR0qQtakcUEqI,14639
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=rfzdgl-vWkCyhLzXNNuWgPLK59eiYypQb6TdZKymUqk,21469
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=UVsXMn6jTjm_ofVFt218jAS11s4RV4zD781vP4im-GI,3371
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=sv55oynfqgpHEpo1OEtVDri-5fywzPhDlR85qaWikvY,16195
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=9LHK2XHDjbyTLjmDQieskG8fqbiSpRnFOkfrutDnOTE,2258
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=cOEKnMRzXUW3bwmiOlgn3E1-B38osng0dh2pDykwITY,2410
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=IdKR2yaQGY1iRgKXgxF1UtlyuJEmPXWRh0rGFXv7Z_U,18111
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=MUvcOg9U5J6TnWBel8eht9PdI9BfAKjUxmfjP_ZXx9o,10484
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=jCwPXmnjj8u2ytBRAS_NU7ga0uB7k3_TZY6dZSIMl9Y,15253
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=cSlvpHFOqpiZ89pPZ5GDp9Hu6j6uj-5_h21FWm_L-KM,1248
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=SwYg3aCLmam70mlkJURYN42IggmbxviFnMUJ72WYE4g,42940
80
- trilogy/dialect/bigquery.py,sha256=4u4SuQ67_Zwyu0czyQnBMDUVlegqir0SA30iEbZEAwU,3575
81
- trilogy/dialect/common.py,sha256=IhW0v5zATvZ2K0vr4Ab4TWpYMKKkGangSpIyqaPYEkw,5762
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=kjA0-14mgrr1smOjt01h0nk6iatLj9tXXC8cMsir084,29782
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=Fjjh6YpLS1PluEymkNtDe698EIgtzZqqPb0kfDEenQc,71080
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=se-gnL3UfrdznVvhNbzzcE5VZxZ18iNMbNMFvRjr30I,14304
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=Ki4M-dG2xUIT_U16kOBXdaZb62S3P7iDrMBY93I4I3Q,132
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.56.dist-info/METADATA,sha256=f1YsxdSUDeoSLE8O430eEFxgXPs5DZpnmLNhab9f9U4,9095
113
- pytrilogy-0.0.3.56.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
114
- pytrilogy-0.0.3.56.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
115
- pytrilogy-0.0.3.56.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
116
- pytrilogy-0.0.3.56.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
trilogy/__init__.py CHANGED
@@ -4,6 +4,6 @@ from trilogy.dialect.enums import Dialects
4
4
  from trilogy.executor import Executor
5
5
  from trilogy.parser import parse
6
6
 
7
- __version__ = "0.0.3.56"
7
+ __version__ = "0.0.3.60"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -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
@@ -2,3 +2,4 @@ CONSTANT_DATASET: str = "preql_internal_constant_dataset"
2
2
  ALL_ROWS_CONCEPT = "all_rows"
3
3
  INTERNAL_NAMESPACE = "__preql_internal"
4
4
  PERSISTED_CONCEPT_PREFIX = "__pre_persist"
5
+ UNNEST_NAME = "_unnest_alias"
trilogy/core/enums.py CHANGED
@@ -7,6 +7,7 @@ class UnnestMode(Enum):
7
7
  DIRECT = "direct"
8
8
  CROSS_APPLY = "cross_apply"
9
9
  CROSS_JOIN = "cross_join"
10
+ CROSS_JOIN_UNNEST = "cross_join_unnest"
10
11
  CROSS_JOIN_ALIAS = "cross_join_alias"
11
12
  SNOWFLAKE = "snowflake"
12
13
 
@@ -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
 
@@ -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(f"finished checking for {type(rule).__name__} in {loops} loops")
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=context.original_mandatory,
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=context.original_mandatory,
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
- # assig
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 self._try_synonym_resolution(root_targets)
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
- parent_node.remove_output_concepts(
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
- if lookup.derivation in (Derivation.CONSTANT,):
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
- concept_to_node(c.with_default_grain())
233
- for c in all_concepts
234
- if "__preql_internal" not in c.address
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.output_concepts:
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"[GEN_SYNONYM_NODE] {padding(depth)}"
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=arguments + non_equivalent_optional,
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 grain required"
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 output_addresses:
612
- new_output.append(mapping[x])
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 output_addresses:
650
- new_output.append(mapping[x])
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
  [
@@ -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.output_concepts,
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
- mapping = {x.address: x for x in cte.output_columns}
556
+
557
557
  return ProcessedQuery(
558
558
  order_by=root_cte.order_by,
559
559
  limit=statement.limit,
560
- output_columns=[mapping[x.address] for x in statement.output_components],
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=c.address.replace(".", "_"),
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[BuildConcept]
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[BuildConcept]
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
- factory.build(
943
- environment.concepts[
944
- DEFAULT_CONCEPTS["query_text"].address
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
 
@@ -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.CROSS_JOIN
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
- address = "anon_function"
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 == UnnestMode.CROSS_JOIN:
99
- return f"CROSS JOIN {render_unnest(unnest_mode, quote_character, join.object_to_unnest, render_expr_func, cte)}"
100
- if unnest_mode == UnnestMode.CROSS_JOIN_ALIAS:
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 BuildConcept, BuildFunction
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[BuildConcept], output_data: list[Any]
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(self, statement) -> Optional[CursorResult]:
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,
@@ -667,8 +667,14 @@ class ParseToObjects(Transformer):
667
667
  environment=self.environment,
668
668
  metadata=metadata,
669
669
  )
670
-
671
- if purpose and purpose != Purpose.AUTO and concept.purpose != purpose:
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
  )
@@ -379,7 +379,7 @@
379
379
 
380
380
  int_lit: /\-?[0-9]+/
381
381
 
382
- float_lit: /[0-9]*\.[0-9]+/
382
+ float_lit: /\-?[0-9]*\.[0-9]+/
383
383
 
384
384
  array_lit: "[" (literal ",")* literal ","? "]"()
385
385
 
trilogy/std/date.preql CHANGED
@@ -2,9 +2,12 @@
2
2
 
3
3
  type year int;
4
4
  type month int;
5
+ type month_name string;
5
6
  type week int;
6
7
  type day int;
7
8
  type hour int;
8
9
  type minute int;
9
10
  type second int;
10
- type day_of_week int;
11
+ type day_of_week int;
12
+ type day_of_week_name string;
13
+
@@ -0,0 +1,6 @@
1
+
2
+ type rank int;
3
+ type score int;
4
+ type position int;
5
+ type index int;
6
+ type grade int;