pytrilogy 0.0.3.102__py3-none-any.whl → 0.0.3.104__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.102.dist-info → pytrilogy-0.0.3.104.dist-info}/METADATA +2 -1
  2. {pytrilogy-0.0.3.102.dist-info → pytrilogy-0.0.3.104.dist-info}/RECORD +33 -31
  3. trilogy/__init__.py +1 -1
  4. trilogy/constants.py +1 -1
  5. trilogy/core/models/execute.py +1 -6
  6. trilogy/core/optimization.py +13 -4
  7. trilogy/core/optimizations/__init__.py +2 -0
  8. trilogy/core/optimizations/hide_unused_concept.py +51 -0
  9. trilogy/core/optimizations/predicate_pushdown.py +9 -1
  10. trilogy/core/processing/concept_strategies_v3.py +35 -14
  11. trilogy/core/processing/discovery_node_factory.py +6 -6
  12. trilogy/core/processing/discovery_utility.py +163 -14
  13. trilogy/core/processing/node_generators/basic_node.py +1 -0
  14. trilogy/core/processing/node_generators/common.py +1 -0
  15. trilogy/core/processing/node_generators/filter_node.py +0 -10
  16. trilogy/core/processing/node_generators/group_node.py +36 -0
  17. trilogy/core/processing/node_generators/multiselect_node.py +1 -1
  18. trilogy/core/processing/node_generators/node_merge_node.py +2 -6
  19. trilogy/core/processing/node_generators/rowset_node.py +1 -21
  20. trilogy/core/processing/node_generators/union_node.py +1 -1
  21. trilogy/core/processing/node_generators/unnest_node.py +24 -8
  22. trilogy/core/processing/nodes/base_node.py +13 -3
  23. trilogy/core/processing/nodes/group_node.py +9 -91
  24. trilogy/core/processing/nodes/merge_node.py +9 -0
  25. trilogy/core/processing/utility.py +8 -0
  26. trilogy/dialect/base.py +20 -7
  27. trilogy/dialect/common.py +5 -0
  28. trilogy/std/color.preql +3 -0
  29. trilogy/std/display.preql +3 -3
  30. {pytrilogy-0.0.3.102.dist-info → pytrilogy-0.0.3.104.dist-info}/WHEEL +0 -0
  31. {pytrilogy-0.0.3.102.dist-info → pytrilogy-0.0.3.104.dist-info}/entry_points.txt +0 -0
  32. {pytrilogy-0.0.3.102.dist-info → pytrilogy-0.0.3.104.dist-info}/licenses/LICENSE.md +0 -0
  33. {pytrilogy-0.0.3.102.dist-info → pytrilogy-0.0.3.104.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.102
3
+ Version: 0.0.3.104
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -19,6 +19,7 @@ Requires-Dist: sqlalchemy<2.0.0
19
19
  Requires-Dist: networkx
20
20
  Requires-Dist: pyodbc
21
21
  Requires-Dist: pydantic
22
+ Requires-Dist: duckdb<1.4.0
22
23
  Requires-Dist: duckdb-engine
23
24
  Requires-Dist: click
24
25
  Provides-Extra: postgres
@@ -1,6 +1,6 @@
1
- pytrilogy-0.0.3.102.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
- trilogy/__init__.py,sha256=WZdbHlLqyuYo0xjcYkV5QDokunZLDlhGeibgoay48uc,304
3
- trilogy/constants.py,sha256=ohmro6so7PPNp2ruWQKVc0ijjXYPOyRrxB9LI8dr3TU,1746
1
+ pytrilogy-0.0.3.104.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
+ trilogy/__init__.py,sha256=HyZF9WId40s9G3BjFS2OBMeHI7XNeE-YU1cFLvNOSWk,304
3
+ trilogy/constants.py,sha256=g_zkVCNjGop6coZ1kM8eXXAzCnUN22ldx3TYFz0E9sc,1747
4
4
  trilogy/engine.py,sha256=3MiADf5MKcmxqiHBuRqiYdsXiLj7oitDfVvXvHrfjkA,2178
5
5
  trilogy/executor.py,sha256=KgCAQhHPT-j0rPkBbALX0f84W9-Q-bkjHayGuavg99w,16490
6
6
  trilogy/parser.py,sha256=o4cfk3j3yhUFoiDKq9ZX_GjBF3dKhDjXEwb63rcBkBM,293
@@ -18,7 +18,7 @@ trilogy/core/exceptions.py,sha256=axkVXYJYQXCCwMHwlyDA232g4tCOwdCZUt7eHeUMDMg,28
18
18
  trilogy/core/functions.py,sha256=sdV6Z3NUVfwL1d18eNcaAXllVNqzLez23McsJ6xIp7M,33182
19
19
  trilogy/core/graph_models.py,sha256=4EWFTHGfYd72zvS2HYoV6hm7nMC_VEd7vWr6txY-ig0,3400
20
20
  trilogy/core/internal.py,sha256=r9QagDB2GvpqlyD_I7VrsfbVfIk5mnok2znEbv72Aa4,2681
21
- trilogy/core/optimization.py,sha256=ojpn-p79lr03SSVQbbw74iPCyoYpDYBmj1dbZ3oXCjI,8860
21
+ trilogy/core/optimization.py,sha256=Km0ITEx9n6Iv5ReX6tm4uXO5uniSv_ooahycNNiET3g,9212
22
22
  trilogy/core/query_processor.py,sha256=uqygDJqkjIH4vLP-lbGRgTN7rRcYEkr3KGqNimNw_80,20345
23
23
  trilogy/core/utility.py,sha256=3VC13uSQWcZNghgt7Ot0ZTeEmNqs__cx122abVq9qhM,410
24
24
  trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -28,42 +28,43 @@ trilogy/core/models/build_environment.py,sha256=mpx7MKGc60fnZLVdeLi2YSREy7eQbQYy
28
28
  trilogy/core/models/core.py,sha256=iT9WdZoiXeglmUHWn6bZyXCTBpkApTGPKtNm_Mhbu_g,12987
29
29
  trilogy/core/models/datasource.py,sha256=wogTevZ-9CyUW2a8gjzqMCieircxi-J5lkI7EOAZnck,9596
30
30
  trilogy/core/models/environment.py,sha256=hwTIRnJgaHUdCYof7U5A9NPitGZ2s9yxqiW5O2SaJ9Y,28759
31
- trilogy/core/models/execute.py,sha256=lsNzNjS3nZvoW5CHjYwxDTwBe502NZyytpK1eq8CwW4,42357
32
- trilogy/core/optimizations/__init__.py,sha256=YH2-mGXZnVDnBcWVi8vTbrdw7Qs5TivG4h38rH3js_I,290
31
+ trilogy/core/models/execute.py,sha256=pdL3voYB4dCQR_KMHwFaofP3ZpRbALRC2ELHueWyTko,42191
32
+ trilogy/core/optimizations/__init__.py,sha256=yspWc25M5SgAuvXYoSt5J8atyPbDlOfsKjIo5yGD9s4,368
33
33
  trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnbnb6vdVIztUfFQZsGnM,513
34
+ trilogy/core/optimizations/hide_unused_concept.py,sha256=DbsP8NqQOxmPv9omDOoFNPUGObUkqsRRNrr5d1xDxx4,1962
34
35
  trilogy/core/optimizations/inline_datasource.py,sha256=2sWNRpoRInnTgo9wExVT_r9RfLAQHI57reEV5cGHUcg,4329
35
- trilogy/core/optimizations/predicate_pushdown.py,sha256=g4AYE8Aw_iMlAh68TjNXGP754NTurrDduFECkUjoBnc,9399
36
+ trilogy/core/optimizations/predicate_pushdown.py,sha256=5ubatgq1IwWQ4L2FDt4--y168YLuGP-vwqH0m8IeTIw,9786
36
37
  trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
- trilogy/core/processing/concept_strategies_v3.py,sha256=tvSN_aiqb1H7LkTl96vj7YK_DKcq_1nDdRJ69wZCLc8,22158
38
- trilogy/core/processing/discovery_node_factory.py,sha256=5QVYUsci_h6iYWhS0GCoDow2tSAipiBW1OyTRX-g_L8,15581
39
- trilogy/core/processing/discovery_utility.py,sha256=Xntgug6VnEF96uw5Zwen1qMEUwKjqrm_ZDUr4i4tc1U,5595
38
+ trilogy/core/processing/concept_strategies_v3.py,sha256=AcMU1d5uCo8I1PFCkBtmcC6iFmM9vN6xSdKxSVMGfpA,23080
39
+ trilogy/core/processing/discovery_node_factory.py,sha256=p23jiiHyhrW-Q8ndbnRlqMHJKT8ZqPOA89SzE4xaFFo,15445
40
+ trilogy/core/processing/discovery_utility.py,sha256=wIuLsE6yuVykeYZdIqRSagivDNU3-ooiS7z6in4yqho,11518
40
41
  trilogy/core/processing/discovery_validation.py,sha256=eZ4HfHMpqZLI8MGG2jez8arS8THs6ceuVrQFIY6gXrU,5364
41
42
  trilogy/core/processing/graph_utils.py,sha256=8QUVrkE9j-9C1AyrCb1nQEh8daCe0u1HuXl-Te85lag,1205
42
- trilogy/core/processing/utility.py,sha256=PGQKZgX58kI3gG4nprY8HRGayc2D8fF5RmbvBhQj8ok,23319
43
+ trilogy/core/processing/utility.py,sha256=1_oNnk6lWiy-D7LKYr07kU_v7iAM4i6ITUAS4bIiCr4,23444
43
44
  trilogy/core/processing/node_generators/__init__.py,sha256=iVJ-crowPxYeut-hFjyEjfibKIDq7PfB4LEuDAUCjGY,943
44
- trilogy/core/processing/node_generators/basic_node.py,sha256=0Uhnf07056SBbRkt-wYLw4DZqsFR6jztGLUaE9ebPZs,5577
45
- trilogy/core/processing/node_generators/common.py,sha256=PdysdroW9DUADP7f5Wv_GKPUyCTROZV1g3L45fawxi8,9443
45
+ trilogy/core/processing/node_generators/basic_node.py,sha256=74LoVZXLinRvSzk2LmI1kwza96TnuH3ELoYRIbHB29A,5578
46
+ trilogy/core/processing/node_generators/common.py,sha256=xF32Kf6B08dZgKs2SOow1HomptSiSC057GCUCHFlS5s,9464
46
47
  trilogy/core/processing/node_generators/constant_node.py,sha256=LfpDq2WrBRZ3tGsLxw77LuigKfhbteWWh9L8BGdMGwk,1146
47
- trilogy/core/processing/node_generators/filter_node.py,sha256=ArBsQJl-4fWBJWCE28CRQ7UT7ErnFfbcseoQQZrBodY,11220
48
- trilogy/core/processing/node_generators/group_node.py,sha256=yqOWl5TCV4PrdJua4OJkPUIHkljaLoSW2Y8eRAmVddQ,6733
48
+ trilogy/core/processing/node_generators/filter_node.py,sha256=ndPznkcFu_cdCNgaRpgot8oqnzdHv4KAIfjeUIzrE2w,10816
49
+ trilogy/core/processing/node_generators/group_node.py,sha256=NdK1rl6Ze94XFWtgeC2dlRiL4pS3lh1ArKGPEltLtnw,8525
49
50
  trilogy/core/processing/node_generators/group_to_node.py,sha256=jKcNCDOY6fNblrdZwaRU0sbUSr9H0moQbAxrGgX6iGA,3832
50
- trilogy/core/processing/node_generators/multiselect_node.py,sha256=GWV5yLmKTe1yyPhN60RG1Rnrn4ktfn9lYYXi_FVU4UI,7061
51
- trilogy/core/processing/node_generators/node_merge_node.py,sha256=1joMV7XpQ9Gpe-d5y7JUMBHIqakV5wFJi3Mtvs4UcL4,23415
51
+ trilogy/core/processing/node_generators/multiselect_node.py,sha256=a505AEixjsjp5jI8Ng3H5KF_AaehkS6HfRfTef64l_o,7063
52
+ trilogy/core/processing/node_generators/node_merge_node.py,sha256=hNcZxnDLTZyYJWfojg769zH9HB9PfZfESmpN1lcHWXg,23172
52
53
  trilogy/core/processing/node_generators/recursive_node.py,sha256=l5zdh0dURKwmAy8kK4OpMtZfyUEQRk6N-PwSWIyBpSM,2468
53
- trilogy/core/processing/node_generators/rowset_node.py,sha256=5L5u6xz1In8EaHQdcYgR2si-tz9WB9YLXURo4AkUT9A,6630
54
+ trilogy/core/processing/node_generators/rowset_node.py,sha256=MuVNIexXhqGONho_mewqMOwaYXNUnjjvyPvk_RDGNYE,5943
54
55
  trilogy/core/processing/node_generators/select_merge_node.py,sha256=KQvGoNT5ZBWQ_caEomRTtG1PKZC7OPT4PKfY0QmwMGE,22270
55
56
  trilogy/core/processing/node_generators/select_node.py,sha256=Ta1G39V94gjX_AgyZDz9OqnwLz4BjY3D6Drx9YpziMQ,3555
56
57
  trilogy/core/processing/node_generators/synonym_node.py,sha256=AnAsa_Wj50NJ_IK0HSgab_7klYmKVrv0WI1uUe-GvEY,3766
57
- trilogy/core/processing/node_generators/union_node.py,sha256=VNo6Oey4p8etU9xrOh2oTT2lIOTvY6PULUPRvVa2uxU,2877
58
- trilogy/core/processing/node_generators/unnest_node.py,sha256=w9vhPzASz53QPASLqFcLDdR9eY132tgVUcp3QolD5Jw,3726
58
+ trilogy/core/processing/node_generators/union_node.py,sha256=NxQbnRRoYMI4WjMeph41yk4E6yipj53qdGuNt-Mozxw,2818
59
+ trilogy/core/processing/node_generators/unnest_node.py,sha256=7uOZzBidEEKeZE0VW_XlgHGhEYf_snEHtV8UgJ_ZjyY,4048
59
60
  trilogy/core/processing/node_generators/window_node.py,sha256=A90linr4pkZtTNfn9k2YNLqrJ_SFII3lbHxB-BC6mI8,6688
60
61
  trilogy/core/processing/node_generators/select_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
62
  trilogy/core/processing/node_generators/select_helpers/datasource_injection.py,sha256=m2YQ4OmG0N2O61a7NEq1ZzbTa7JsCC00lxB2ymjcYRI,8224
62
63
  trilogy/core/processing/nodes/__init__.py,sha256=zTge1EzwzEydlcMliIFO_TT7h7lS8l37lyZuQDir1h0,5487
63
- trilogy/core/processing/nodes/base_node.py,sha256=C_CjlOzlGMXckyV0b_PJZerpopNesRCKfambMq7Asvc,18221
64
+ trilogy/core/processing/nodes/base_node.py,sha256=6LPQ5zP_dZJ6-k_dmX9ZSLsHaQMHgqiR5DEylpHYGZA,18478
64
65
  trilogy/core/processing/nodes/filter_node.py,sha256=5VtRfKbCORx0dV-vQfgy3gOEkmmscL9f31ExvlODwvY,2461
65
- trilogy/core/processing/nodes/group_node.py,sha256=njz-5T7OJ3-kaBC7EhdtPra3G77HnI7apjUwMGhUeXo,10569
66
- trilogy/core/processing/nodes/merge_node.py,sha256=daJywBxh44Gqk-7eTiXbYtY7xo6O6fNvqX-DagTOTmE,16231
66
+ trilogy/core/processing/nodes/group_node.py,sha256=sKsRP_BWEKg6z63T1X5ZlkJF2IMif0IEbVWTk-cdOH8,7100
67
+ trilogy/core/processing/nodes/merge_node.py,sha256=uc0tlz30Yt9SnCwLhMcWuPVbXLzm3dzy0XqbyirqqTo,16521
67
68
  trilogy/core/processing/nodes/recursive_node.py,sha256=k0rizxR8KE64ievfHx_GPfQmU8QAP118Laeyq5BLUOk,1526
68
69
  trilogy/core/processing/nodes/select_node_v2.py,sha256=IWyKyNgFlV8A2S1FUTPdIaogg6PzaHh-HmQo6v24sbg,8862
69
70
  trilogy/core/processing/nodes/union_node.py,sha256=hLAXXVWqEgMWi7dlgSHfCF59fon64av14-uPgJzoKzM,1870
@@ -81,9 +82,9 @@ trilogy/core/validation/datasource.py,sha256=nJeEFyb6iMBwlEVdYVy1vLzAbdRZwOsUjGx
81
82
  trilogy/core/validation/environment.py,sha256=ymvhQyt7jLK641JAAIQkqjQaAmr9C5022ILzYvDgPP0,2835
82
83
  trilogy/core/validation/fix.py,sha256=Z818UFNLxndMTLiyhB3doLxIfnOZ-16QGvVFWuD7UsA,3750
83
84
  trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
84
- trilogy/dialect/base.py,sha256=d2gXfa5Jh3uyN9H9MxG53JT-xQQgntq2X7EprobJYUc,49698
85
+ trilogy/dialect/base.py,sha256=hFX0_3N-m3ZRTCyv1S650a8OPlx9qjp5Zh8wzTBx6E8,50338
85
86
  trilogy/dialect/bigquery.py,sha256=XS3hpybeowgfrOrkycAigAF3NX2YUzTzfgE6f__2fT4,4316
86
- trilogy/dialect/common.py,sha256=_MarnMWRBn3VcNt3k5VUdFrwH6oHzGdNQquSpHNLq4o,5644
87
+ trilogy/dialect/common.py,sha256=cUI7JMmpG_A5KcaxRI-GoyqwLMD6jTf0JJhgcOdwQK4,5833
87
88
  trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
88
89
  trilogy/dialect/dataframe.py,sha256=RUbNgReEa9g3pL6H7fP9lPTrAij5pkqedpZ99D8_5AE,1522
89
90
  trilogy/dialect/duckdb.py,sha256=JoUvQ19WvgxoaJkGLM7DPXOd1H0394k3vBiblksQzOI,5631
@@ -109,16 +110,17 @@ trilogy/parsing/trilogy.lark,sha256=6eBDD6d4D9N1Nnn4CtmaoB-NpOpjHrEn5oi0JykAlbE,
109
110
  trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
110
111
  trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
111
112
  trilogy/std/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
113
+ trilogy/std/color.preql,sha256=sS9AXLDkECuDbNGnBMi2KnUuJukyVZVThKI9mP-ZOZI,50
112
114
  trilogy/std/date.preql,sha256=HWZm4t4HWyxr5geWRsY05RnHBVDMci8z8YA2cu0-OOw,188
113
- trilogy/std/display.preql,sha256=S20HW8qbShBc4OZPcHYiRlLdcaBp9dwruozWBoXKscs,293
115
+ trilogy/std/display.preql,sha256=ZJ08crsZnC3kaWwNUrMB1ZH5j6DUUbz8RaUgihA8sm4,299
114
116
  trilogy/std/geography.preql,sha256=1A9Sq5PPMBnEPPf7f-rPVYxJfsnWpQ8oV_k4Fm3H2dU,675
115
117
  trilogy/std/metric.preql,sha256=DRECGhkMyqfit5Fl4Ut9zbWrJuSMI1iO9HikuyoBpE0,421
116
118
  trilogy/std/money.preql,sha256=XWwvAV3WxBsHX9zfptoYRnBigcfYwrYtBHXTME0xJuQ,2082
117
119
  trilogy/std/net.preql,sha256=WZCuvH87_rZntZiuGJMmBDMVKkdhTtxeHOkrXNwJ1EE,416
118
120
  trilogy/std/ranking.preql,sha256=LDoZrYyz4g3xsII9XwXfmstZD-_92i1Eox1UqkBIfi8,83
119
121
  trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
120
- pytrilogy-0.0.3.102.dist-info/METADATA,sha256=fQKKWHDkY9Nhofow6RO22oMSXp91H-vOD5d3kk3S-V8,11811
121
- pytrilogy-0.0.3.102.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
122
- pytrilogy-0.0.3.102.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
123
- pytrilogy-0.0.3.102.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
124
- pytrilogy-0.0.3.102.dist-info/RECORD,,
122
+ pytrilogy-0.0.3.104.dist-info/METADATA,sha256=IJmkrwnxe7gz3s89ZYVrDe6SkRY2cf6xNpmj5GTXkSE,11839
123
+ pytrilogy-0.0.3.104.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
124
+ pytrilogy-0.0.3.104.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
125
+ pytrilogy-0.0.3.104.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
126
+ pytrilogy-0.0.3.104.dist-info/RECORD,,
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.102"
7
+ __version__ = "0.0.3.104"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
trilogy/constants.py CHANGED
@@ -37,7 +37,7 @@ class Comments:
37
37
 
38
38
  show: bool = False
39
39
  basic: bool = True
40
- joins: bool = True
40
+ joins: bool = False
41
41
  nullable: bool = True
42
42
  partial: bool = True
43
43
  source_map: bool = False
@@ -711,8 +711,6 @@ class QueryDatasource(BaseModel):
711
711
  f" {[c.address for c in self.output_concepts]} concepts and"
712
712
  f" {other.name} with {[c.address for c in other.output_concepts]} concepts"
713
713
  )
714
- logger.info(self.source_map)
715
- logger.info(other.source_map)
716
714
 
717
715
  merged_datasources: dict[str, Union[BuildDatasource, "QueryDatasource"]] = {}
718
716
 
@@ -816,10 +814,7 @@ class QueryDatasource(BaseModel):
816
814
  use_raw_name,
817
815
  force_alias=force_alias,
818
816
  )
819
- except ValueError as e:
820
- from trilogy.constants import logger
821
-
822
- logger.debug(e)
817
+ except ValueError:
823
818
  continue
824
819
  existing = [c.with_grain(self.grain) for c in self.output_concepts]
825
820
  if concept in existing:
@@ -5,6 +5,7 @@ from trilogy.core.models.build import (
5
5
  )
6
6
  from trilogy.core.models.execute import CTE, RecursiveCTE, UnionCTE
7
7
  from trilogy.core.optimizations import (
8
+ HideUnusedConcepts,
8
9
  InlineDatasource,
9
10
  OptimizationRule,
10
11
  PredicatePushdown,
@@ -84,11 +85,18 @@ def filter_irrelevant_ctes(
84
85
  # child.existence_source_map[x2].append(parent.name)
85
86
  # else:
86
87
  relevant_ctes.add(cte.name)
87
- for cte in cte.parent_ctes:
88
- recurse(cte, inverse_map)
88
+
89
+ for parent in cte.parent_ctes:
90
+ if parent.name in relevant_ctes:
91
+ logger.info(
92
+ f"[Optimization][Irrelevent CTE filtering] Already visited {parent.name} when visting {cte.name}, potential recursive dag"
93
+ )
94
+ continue
95
+
96
+ recurse(parent, inverse_map)
89
97
  if isinstance(cte, UnionCTE):
90
- for cte in cte.internal_ctes:
91
- recurse(cte, inverse_map)
98
+ for internal in cte.internal_ctes:
99
+ recurse(internal, inverse_map)
92
100
 
93
101
  inverse_map = gen_inverse_map(input)
94
102
  recurse(root_cte, inverse_map)
@@ -220,6 +228,7 @@ def optimize_ctes(
220
228
  REGISTERED_RULES.append(PredicatePushdown())
221
229
  if CONFIG.optimizations.predicate_pushdown:
222
230
  REGISTERED_RULES.append(PredicatePushdownRemove())
231
+ REGISTERED_RULES.append(HideUnusedConcepts())
223
232
  for rule in REGISTERED_RULES:
224
233
  loops = 0
225
234
  complete = False
@@ -1,4 +1,5 @@
1
1
  from .base_optimization import OptimizationRule
2
+ from .hide_unused_concept import HideUnusedConcepts
2
3
  from .inline_datasource import InlineDatasource
3
4
  from .predicate_pushdown import PredicatePushdown, PredicatePushdownRemove
4
5
 
@@ -7,4 +8,5 @@ __all__ = [
7
8
  "InlineDatasource",
8
9
  "PredicatePushdown",
9
10
  "PredicatePushdownRemove",
11
+ "HideUnusedConcepts",
10
12
  ]
@@ -0,0 +1,51 @@
1
+ from trilogy.core.models.build import (
2
+ BuildConcept,
3
+ )
4
+ from trilogy.core.models.execute import CTE, UnionCTE
5
+ from trilogy.core.optimizations.base_optimization import OptimizationRule
6
+
7
+
8
+ class HideUnusedConcepts(OptimizationRule):
9
+ def __init__(self, *args, **kwargs) -> None:
10
+ super().__init__(*args, **kwargs)
11
+
12
+ def optimize(
13
+ self, cte: CTE | UnionCTE, inverse_map: dict[str, list[CTE | UnionCTE]]
14
+ ) -> bool:
15
+ used = set()
16
+ from trilogy.dialect.base import BaseDialect
17
+
18
+ renderer = BaseDialect()
19
+ children = inverse_map.get(cte.name, [])
20
+ if not children:
21
+ return False
22
+ for v in children:
23
+ self.log(f"Analyzing usage of {cte.name} in {v.name}")
24
+ renderer.render_cte(v)
25
+ used = renderer.used_map.get(cte.name, set())
26
+ self.log(f"Used concepts for {cte.name}: {used} from {renderer.used_map}")
27
+ add_to_hidden: list[BuildConcept] = []
28
+ for concept in cte.output_columns:
29
+ if concept.address not in used:
30
+ add_to_hidden.append(concept)
31
+ newly_hidden = [
32
+ x.address for x in add_to_hidden if x.address not in cte.hidden_concepts
33
+ ]
34
+ non_hidden = [
35
+ x for x in cte.output_columns if x.address not in cte.hidden_concepts
36
+ ]
37
+ if not newly_hidden or len(non_hidden) <= 1:
38
+ return False
39
+ self.log(
40
+ f"Hiding unused concepts {[x.address for x in add_to_hidden]} from {cte.name} (used: {used}, all: {[x.address for x in cte.output_columns]})"
41
+ )
42
+ candidates = [
43
+ x.address
44
+ for x in cte.output_columns
45
+ if x.address not in used and x.address not in cte.hidden_concepts
46
+ ]
47
+ if len(candidates) == len(set([x.address for x in cte.output_columns])):
48
+ # pop one out
49
+ candidates.pop()
50
+ cte.hidden_concepts = set(candidates)
51
+ return True
@@ -1,5 +1,6 @@
1
1
  from trilogy.core.enums import (
2
2
  BooleanOperator,
3
+ SourceType,
3
4
  )
4
5
  from trilogy.core.models.build import (
5
6
  BuildComparison,
@@ -59,12 +60,19 @@ class PredicatePushdown(OptimizationRule):
59
60
  )
60
61
  return False
61
62
  materialized = {k for k, v in parent_cte.source_map.items() if v != []}
63
+
62
64
  if not row_conditions or not materialized:
63
65
  return False
64
66
  output_addresses = {x.address for x in parent_cte.output_columns}
65
67
  # if any of the existence conditions are created on the asset, we can't push up to it
66
68
  if existence_conditions and existence_conditions.intersection(output_addresses):
67
69
  return False
70
+ if existence_conditions:
71
+ self.log(
72
+ f"Not pushing up existence {candidate} to {parent_cte.name} as it is a filter node"
73
+ )
74
+ if parent_cte.source.source_type == SourceType.FILTER:
75
+ return False
68
76
  # if it's a root datasource, we can filter on _any_ of the output concepts
69
77
  if parent_cte.is_root_datasource:
70
78
  extra_check = {
@@ -81,7 +89,7 @@ class PredicatePushdown(OptimizationRule):
81
89
  children = inverse_map.get(parent_cte.name, [])
82
90
  if all([is_child_of(candidate, child.condition) for child in children]):
83
91
  self.log(
84
- f"All concepts are found on {parent_cte.name} with existing {parent_cte.condition} and all it's {len(children)} children include same filter; pushing up {candidate}"
92
+ f"All concepts [{row_conditions}] and existence conditions [{existence_conditions}] not block pushup of [{output_addresses}]found on {parent_cte.name} with existing {parent_cte.condition} and all it's {len(children)} children include same filter; pushing up {candidate}"
85
93
  )
86
94
  if parent_cte.condition and not is_scalar_condition(
87
95
  parent_cte.condition
@@ -19,7 +19,7 @@ from trilogy.core.processing.discovery_utility import (
19
19
  LOGGER_PREFIX,
20
20
  depth_to_prefix,
21
21
  get_priority_concept,
22
- group_if_required,
22
+ group_if_required_v2,
23
23
  )
24
24
  from trilogy.core.processing.discovery_validation import (
25
25
  ValidationResult,
@@ -66,7 +66,19 @@ def generate_candidates_restrictive(
66
66
 
67
67
  # if it's single row, joins are irrelevant. Fetch without keys.
68
68
  if priority_concept.granularity == Granularity.SINGLE_ROW:
69
- return [], conditions
69
+ logger.info("Have single row concept, including only other single row optional")
70
+ optional = (
71
+ [
72
+ x
73
+ for x in candidates
74
+ if x.granularity == Granularity.SINGLE_ROW
75
+ and x.address not in priority_concept.pseudonyms
76
+ and priority_concept.address not in x.pseudonyms
77
+ ]
78
+ if priority_concept.derivation == Derivation.AGGREGATE
79
+ else []
80
+ )
81
+ return optional, conditions
70
82
 
71
83
  if conditions and priority_concept.derivation in ROOT_DERIVATIONS:
72
84
  logger.info(
@@ -374,15 +386,21 @@ def generate_loop_completion(context: LoopContext, virtual: set[str]) -> Strateg
374
386
  logger.info(
375
387
  f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Found different non-virtual output concepts ({non_virtual_difference_values}), removing condition injected values by setting outputs to {[x.address for x in output.output_concepts if x.address in non_virtual_output]}"
376
388
  )
377
- output.set_output_concepts(
378
- [
379
- x
380
- for x in output.output_concepts
381
- if x.address in non_virtual_output
382
- or any(c in non_virtual_output for c in x.pseudonyms)
383
- ],
384
- rebuild=False,
385
- )
389
+ # output.set_output_concepts(
390
+ # [
391
+ # x
392
+ # for x in output.output_concepts
393
+ # if x.address not in non_virtual_difference_values
394
+ # or any(c in non_virtual_output for c in x.pseudonyms)
395
+ # ],
396
+ # rebuild=True,
397
+ # change_visibility=False
398
+ # )
399
+ # output.set_output_concepts(context.original_mandatory)
400
+
401
+ # if isinstance(output, MergeNode):
402
+ # output.force_group = True
403
+ # output.rebuild_cache()
386
404
 
387
405
  logger.info(
388
406
  f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Source stack has single node, returning that {type(output)}"
@@ -416,14 +434,17 @@ def generate_loop_completion(context: LoopContext, virtual: set[str]) -> Strateg
416
434
  logger.info(
417
435
  f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Graph is connected, returning {type(output)} node output {[x.address for x in output.usable_outputs]} partial {[c.address for c in output.partial_concepts or []]} with {context.conditions}"
418
436
  )
437
+ from trilogy.core.processing.discovery_utility import group_if_required_v2
438
+
419
439
  if condition_required and context.conditions and non_virtual_different:
420
440
  logger.info(
421
441
  f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Conditions {context.conditions} were injected, checking if we need a group to restore grain"
422
442
  )
423
- return group_if_required(
443
+ return group_if_required_v2(
424
444
  output, context.original_mandatory, context.environment
425
445
  )
426
- return output
446
+
447
+ return group_if_required_v2(output, context.original_mandatory, context.environment)
427
448
 
428
449
 
429
450
  def _search_concepts(
@@ -588,4 +609,4 @@ def source_query_concepts(
588
609
  logger.info(
589
610
  f"{depth_to_prefix(0)}{LOGGER_PREFIX} final concepts are {[x.address for x in final]}"
590
611
  )
591
- return group_if_required(root, output_concepts, environment)
612
+ return group_if_required_v2(root, output_concepts, environment)
@@ -177,7 +177,12 @@ def _generate_union_node(ctx: NodeGenerationContext) -> StrategyNode | None:
177
177
  def _generate_aggregate_node(ctx: NodeGenerationContext) -> StrategyNode | None:
178
178
  # Filter out constants to avoid multiplication issues
179
179
  agg_optional = [
180
- x for x in ctx.local_optional if x.granularity != Granularity.SINGLE_ROW
180
+ x
181
+ for x in ctx.local_optional
182
+ if not (
183
+ x.granularity == Granularity.SINGLE_ROW
184
+ and x.derivation != Derivation.AGGREGATE
185
+ )
181
186
  ]
182
187
 
183
188
  logger.info(
@@ -376,11 +381,6 @@ class RootNodeHandler:
376
381
 
377
382
  if pseudonyms:
378
383
  expanded.add_output_concepts(pseudonyms)
379
- logger.info(
380
- f"{depth_to_prefix(self.ctx.depth)}{LOGGER_PREFIX} "
381
- f"Hiding pseudonyms {[c.address for c in pseudonyms]}"
382
- )
383
- expanded.hide_output_concepts(pseudonyms)
384
384
 
385
385
  logger.info(
386
386
  f"{depth_to_prefix(self.ctx.depth)}{LOGGER_PREFIX} "
@@ -1,13 +1,18 @@
1
1
  from typing import List
2
2
 
3
3
  from trilogy.constants import logger
4
- from trilogy.core.enums import Derivation
4
+ from trilogy.core.enums import Derivation, Purpose
5
5
  from trilogy.core.models.build import (
6
6
  BuildConcept,
7
+ BuildDatasource,
8
+ BuildFilterItem,
9
+ BuildGrain,
7
10
  BuildRowsetItem,
8
11
  )
9
12
  from trilogy.core.models.build_environment import BuildEnvironment
13
+ from trilogy.core.models.execute import QueryDatasource, UnnestJoin
10
14
  from trilogy.core.processing.nodes import GroupNode, MergeNode, StrategyNode
15
+ from trilogy.core.processing.utility import GroupRequiredResponse
11
16
 
12
17
 
13
18
  def depth_to_prefix(depth: int) -> str:
@@ -17,31 +22,175 @@ def depth_to_prefix(depth: int) -> str:
17
22
  LOGGER_PREFIX = "[DISCOVERY LOOP]"
18
23
 
19
24
 
20
- def group_if_required(
25
+ def calculate_effective_parent_grain(
26
+ node: QueryDatasource | BuildDatasource,
27
+ ) -> BuildGrain:
28
+ # calculate the effective grain of the parent node
29
+ # this is the union of all parent grains
30
+ if isinstance(node, MergeNode):
31
+ grain = BuildGrain()
32
+ qds = node.resolve()
33
+ if not qds.joins:
34
+ return qds.datasources[0].grain
35
+ for join in qds.joins:
36
+ if isinstance(join, UnnestJoin):
37
+ continue
38
+ pairs = join.concept_pairs or []
39
+ for key in pairs:
40
+ left = key.existing_datasource
41
+ grain += left.grain
42
+ keys = [key.right for key in pairs]
43
+ join_grain = BuildGrain.from_concepts(keys)
44
+ if join_grain == join.right_datasource.grain:
45
+ logger.info(f"irrelevant right join {join}, does not change grain")
46
+ else:
47
+ logger.info(
48
+ f"join changes grain, adding {join.right_datasource.grain} to {grain}"
49
+ )
50
+ grain += join.right_datasource.grain
51
+ return grain
52
+ else:
53
+ return node.grain or BuildGrain()
54
+
55
+
56
+ def check_if_group_required(
57
+ downstream_concepts: List[BuildConcept],
58
+ parents: list[QueryDatasource | BuildDatasource],
59
+ environment: BuildEnvironment,
60
+ depth: int = 0,
61
+ ) -> GroupRequiredResponse:
62
+ padding = "\t" * depth
63
+ target_grain = BuildGrain.from_concepts(
64
+ downstream_concepts,
65
+ environment=environment,
66
+ )
67
+
68
+ comp_grain = BuildGrain()
69
+ for source in parents:
70
+ # comp_grain += source.grain
71
+ comp_grain += calculate_effective_parent_grain(source)
72
+
73
+ # dynamically select if we need to group
74
+ # we must avoid grouping if we are already at grain
75
+ if comp_grain.issubset(target_grain):
76
+
77
+ logger.info(
78
+ f"{padding}{LOGGER_PREFIX} Group requirement check: {comp_grain}, target: {target_grain}, grain is subset of target, no group node required"
79
+ )
80
+ return GroupRequiredResponse(target_grain, comp_grain, False)
81
+ # find out what extra is in the comp grain vs target grain
82
+ difference = [
83
+ environment.concepts[c] for c in (comp_grain - target_grain).components
84
+ ]
85
+ logger.info(
86
+ f"{padding}{LOGGER_PREFIX} Group requirement check: upstream grain: {comp_grain}, desired grain: {target_grain} from , difference {[x.address for x in difference]}"
87
+ )
88
+ for x in difference:
89
+ logger.info(
90
+ f"{padding}{LOGGER_PREFIX} Difference concept {x.address} purpose {x.purpose} keys {x.keys}"
91
+ )
92
+
93
+ # if the difference is all unique properties whose keys are in the source grain
94
+ # we can also suppress the group
95
+ if all(
96
+ [
97
+ x.keys
98
+ and all(
99
+ environment.concepts[z].address in comp_grain.components for z in x.keys
100
+ )
101
+ for x in difference
102
+ ]
103
+ ):
104
+ logger.info(
105
+ f"{padding}{LOGGER_PREFIX} Group requirement check: skipped due to unique property validation"
106
+ )
107
+ return GroupRequiredResponse(target_grain, comp_grain, False)
108
+ if all([x.purpose == Purpose.KEY for x in difference]):
109
+ logger.info(
110
+ f"{padding}{LOGGER_PREFIX} checking if downstream is unique properties of key"
111
+ )
112
+ replaced_grain_raw: list[set[str]] = [
113
+ (
114
+ x.keys or set()
115
+ if x.purpose == Purpose.UNIQUE_PROPERTY
116
+ else set([x.address])
117
+ )
118
+ for x in downstream_concepts
119
+ if x.address in target_grain.components
120
+ ]
121
+ # flatten the list of lists
122
+ replaced_grain = [item for sublist in replaced_grain_raw for item in sublist]
123
+ # if the replaced grain is a subset of the comp grain, we can skip the group
124
+ unique_grain_comp = BuildGrain.from_concepts(
125
+ replaced_grain, environment=environment
126
+ )
127
+ if comp_grain.issubset(unique_grain_comp):
128
+ logger.info(
129
+ f"{padding}{LOGGER_PREFIX} Group requirement check: skipped due to unique property validation"
130
+ )
131
+ return GroupRequiredResponse(target_grain, comp_grain, False)
132
+ logger.info(
133
+ f"{padding}{LOGGER_PREFIX} Checking for grain equivalence for filters and rowsets"
134
+ )
135
+ ngrain = []
136
+ for con in target_grain.components:
137
+ full = environment.concepts[con]
138
+ if full.derivation == Derivation.ROWSET:
139
+ ngrain.append(full.address.split(".", 1)[1])
140
+ elif full.derivation == Derivation.FILTER:
141
+ assert isinstance(full.lineage, BuildFilterItem)
142
+ if isinstance(full.lineage.content, BuildConcept):
143
+ ngrain.append(full.lineage.content.address)
144
+ else:
145
+ ngrain.append(full.address)
146
+ target_grain2 = BuildGrain.from_concepts(
147
+ ngrain,
148
+ environment=environment,
149
+ )
150
+ if comp_grain.issubset(target_grain2):
151
+ logger.info(
152
+ f"{padding}{LOGGER_PREFIX} Group requirement check: {comp_grain}, {target_grain2}, pre rowset grain is subset of target, no group node required"
153
+ )
154
+ return GroupRequiredResponse(target_grain2, comp_grain, False)
155
+
156
+ logger.info(f"{padding}{LOGGER_PREFIX} Group requirement check: group required")
157
+ return GroupRequiredResponse(target_grain, comp_grain, True)
158
+
159
+
160
+ def group_if_required_v2(
21
161
  root: StrategyNode, final: List[BuildConcept], environment: BuildEnvironment
22
162
  ):
23
- if isinstance(root, MergeNode) and root.force_group is True:
24
- return root
25
- elif isinstance(root, GroupNode):
26
- return root
27
- elif GroupNode.check_if_required(
28
- downstream_concepts=final,
29
- parents=[root.resolve()],
30
- environment=environment,
31
- ).required:
163
+ required = check_if_group_required(
164
+ downstream_concepts=final, parents=[root.resolve()], environment=environment
165
+ )
166
+ targets = [
167
+ x
168
+ for x in root.output_concepts
169
+ if x.address in final or any(c in final for c in x.pseudonyms)
170
+ ]
171
+ if required.required:
32
172
  if isinstance(root, MergeNode):
33
173
  root.force_group = True
34
- root.set_output_concepts(final, rebuild=False)
174
+ root.set_output_concepts(targets, rebuild=False, change_visibility=False)
35
175
  root.rebuild_cache()
36
176
  return root
177
+ elif isinstance(root, GroupNode):
178
+ # root.set_output_concepts(final, rebuild=False)
179
+ # root.rebuild_cache()
180
+ return root
37
181
  return GroupNode(
38
- output_concepts=final,
39
- input_concepts=final,
182
+ output_concepts=targets,
183
+ input_concepts=targets,
40
184
  environment=environment,
41
185
  parents=[root],
42
186
  partial_concepts=root.partial_concepts,
43
187
  preexisting_conditions=root.preexisting_conditions,
44
188
  )
189
+ elif isinstance(root, GroupNode):
190
+
191
+ return root
192
+ else:
193
+ root.set_output_concepts(targets, rebuild=False, change_visibility=False)
45
194
  return root
46
195
 
47
196
 
@@ -143,4 +143,5 @@ def gen_basic_node(
143
143
  logger.info(
144
144
  f"{depth_prefix}{LOGGER_PREFIX} Returning basic select for {concept}: input: {[x.address for x in parent_node.input_concepts]} output {[x.address for x in parent_node.output_concepts]} hidden {[x for x in parent_node.hidden_concepts]}"
145
145
  )
146
+
146
147
  return parent_node
@@ -222,6 +222,7 @@ def gen_enrichment_node(
222
222
  parents=[enrich_node, base_node],
223
223
  force_group=False,
224
224
  preexisting_conditions=conditions.conditional if conditions else None,
225
+ depth=depth,
225
226
  )
226
227
 
227
228