pytrilogy 0.0.2.17__py3-none-any.whl → 0.0.2.19__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 (40) hide show
  1. {pytrilogy-0.0.2.17.dist-info → pytrilogy-0.0.2.19.dist-info}/METADATA +12 -8
  2. {pytrilogy-0.0.2.17.dist-info → pytrilogy-0.0.2.19.dist-info}/RECORD +40 -39
  3. trilogy/__init__.py +1 -1
  4. trilogy/constants.py +1 -1
  5. trilogy/core/enums.py +1 -0
  6. trilogy/core/functions.py +11 -0
  7. trilogy/core/models.py +89 -47
  8. trilogy/core/optimization.py +15 -9
  9. trilogy/core/processing/concept_strategies_v3.py +372 -145
  10. trilogy/core/processing/node_generators/basic_node.py +27 -55
  11. trilogy/core/processing/node_generators/common.py +6 -7
  12. trilogy/core/processing/node_generators/filter_node.py +28 -31
  13. trilogy/core/processing/node_generators/group_node.py +27 -5
  14. trilogy/core/processing/node_generators/group_to_node.py +3 -1
  15. trilogy/core/processing/node_generators/multiselect_node.py +3 -0
  16. trilogy/core/processing/node_generators/node_merge_node.py +14 -10
  17. trilogy/core/processing/node_generators/rowset_node.py +12 -12
  18. trilogy/core/processing/node_generators/select_merge_node.py +317 -0
  19. trilogy/core/processing/node_generators/select_node.py +7 -511
  20. trilogy/core/processing/node_generators/unnest_node.py +4 -3
  21. trilogy/core/processing/node_generators/window_node.py +12 -37
  22. trilogy/core/processing/nodes/__init__.py +0 -2
  23. trilogy/core/processing/nodes/base_node.py +69 -20
  24. trilogy/core/processing/nodes/filter_node.py +3 -0
  25. trilogy/core/processing/nodes/group_node.py +18 -17
  26. trilogy/core/processing/nodes/merge_node.py +4 -10
  27. trilogy/core/processing/nodes/select_node_v2.py +28 -14
  28. trilogy/core/processing/nodes/window_node.py +1 -2
  29. trilogy/core/processing/utility.py +51 -4
  30. trilogy/core/query_processor.py +17 -73
  31. trilogy/dialect/base.py +7 -3
  32. trilogy/dialect/duckdb.py +4 -1
  33. trilogy/dialect/sql_server.py +3 -3
  34. trilogy/hooks/query_debugger.py +5 -3
  35. trilogy/parsing/parse_engine.py +66 -38
  36. trilogy/parsing/trilogy.lark +2 -1
  37. {pytrilogy-0.0.2.17.dist-info → pytrilogy-0.0.2.19.dist-info}/LICENSE.md +0 -0
  38. {pytrilogy-0.0.2.17.dist-info → pytrilogy-0.0.2.19.dist-info}/WHEEL +0 -0
  39. {pytrilogy-0.0.2.17.dist-info → pytrilogy-0.0.2.19.dist-info}/entry_points.txt +0 -0
  40. {pytrilogy-0.0.2.17.dist-info → pytrilogy-0.0.2.19.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.2.17
3
+ Version: 0.0.2.19
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -42,15 +42,15 @@ Installation: `pip install pytrilogy`
42
42
 
43
43
  `pytrilogy` can be run locally to parse and execute trilogy model [.preql] files using the `trilogy` CLI tool, or can be run in python by importing the `trilogy` package.
44
44
 
45
- You can read more about the project [here](https://trilogydata.dev/) and try out an interactive demo on the page an interactive demo [here](https://trilogydata.dev/demo).
45
+ You can read more about the project [here](https://trilogydata.dev/) and try out an interactive demo [here](https://trilogydata.dev/demo/).
46
46
 
47
47
  Trilogy:
48
48
  ```sql
49
+ WHERE
50
+ name like '%lvis%'
49
51
  SELECT
50
52
  name,
51
53
  count(name) as name_count
52
- WHERE
53
- name='Elvis'
54
54
  ORDER BY
55
55
  name_count desc
56
56
  LIMIT 10;
@@ -145,7 +145,7 @@ Run the following from the directory the file is in.
145
145
  trilogy run hello.trilogy duckdb
146
146
  ```
147
147
 
148
- ![UI Preview](./docs/hello_world.png)
148
+ ![UI Preview](./hello_world.png)
149
149
 
150
150
  ## Backends
151
151
 
@@ -214,7 +214,7 @@ for row in results:
214
214
 
215
215
  ## Basic Example - CLI
216
216
 
217
- Trilogy can be run through a CLI tool, appropriately named 'trilogy'.
217
+ Trilogy can be run through a CLI tool, also named 'trilogy'.
218
218
 
219
219
  After installing trilogy, you can run the trilogy CLI with two required positional arguments; the first the path to a file or a direct command,
220
220
  and second the dialect to run.
@@ -252,7 +252,7 @@ N/A, only supports default auth. In python you can pass in a custom client.
252
252
 
253
253
  ## More Examples
254
254
 
255
- [Interactive demo](https://trilogydata.dev/demo).
255
+ [Interactive demo](https://trilogydata.dev/demo/).
256
256
 
257
257
  Additional examples can be found in the [public model repository](https://github.com/trilogydata/trilogy-public-models).
258
258
 
@@ -267,11 +267,15 @@ Clone repository and install requirements.txt and requirements-test.txt.
267
267
  Please open an issue first to discuss what you would like to change, and then create a PR against that issue.
268
268
 
269
269
  ## Similar in space
270
+ Trilogy combines two aspects; a semantic layer and a query language. We've covered examples of both below:
271
+
272
+ Python "semantic layers" are tools for defining data access to a warehouse in a more abstract way.
273
+
274
+ - [metricsflow](https://github.com/dbt-labs/metricflow)
270
275
 
271
276
  "Better SQL" has been a popular space. We believe Trilogy takes a different approach then the following,
272
277
  but all are worth checking out. Please open PRs/comment for anything missed!
273
278
 
274
-
275
279
  - [malloy](https://github.com/malloydata/malloy)
276
280
  - [preql](https://github.com/erezsh/Preql)
277
281
  - [PREQL](https://github.com/PRQL/prql)
@@ -1,6 +1,6 @@
1
- trilogy/__init__.py,sha256=nBRkHW4Ndpv-I3-gUX1LDgrm4e5zxD2GFDWRZjwJ7A8,291
1
+ trilogy/__init__.py,sha256=3gRtKqbvnX1RJDJWia2dlhjAU87WHpfzoZZM3KSaFaw,291
2
2
  trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- trilogy/constants.py,sha256=Ijos7_TEajKhZ7OJ_TreEYFddW1V33AVymDDrxP-ZHk,1234
3
+ trilogy/constants.py,sha256=pZkOneh_65f9Ua6NICu1bHAFAbmQxmiXRXS7tsmCWbQ,1235
4
4
  trilogy/engine.py,sha256=R5ubIxYyrxRExz07aZCUfrTsoXCHQ8DKFTDsobXdWdA,1102
5
5
  trilogy/executor.py,sha256=An6YLpHQOt96E7ozRQhwZels2hMsDbh0WV767kKCGU0,11294
6
6
  trilogy/parser.py,sha256=UtuqSiGiCjpMAYgo1bvNq-b7NSzCA5hzbUW31RXaMII,281
@@ -8,75 +8,76 @@ trilogy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  trilogy/utility.py,sha256=zM__8r29EsyDW7K9VOHz8yvZC2bXFzh7xKy3cL7GKsk,707
9
9
  trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  trilogy/core/constants.py,sha256=LL8NLvxb3HRnAjvofyLRXqQJijLcYiXAQYQzGarVD-g,128
11
- trilogy/core/enums.py,sha256=BRYqy-NgIacCYTJo0B11m5XQWSHq5pfxhoLd8pzA3ho,6025
11
+ trilogy/core/enums.py,sha256=A9VC0lbP5eo9sndm2TzA-nNJRRmvbjE918ZiEXtcQ_c,6043
12
12
  trilogy/core/env_processor.py,sha256=l7TAB0LalxjTYJdTlcmFIkLXuyxa9lrenWLeZfa9qw0,2276
13
13
  trilogy/core/environment_helpers.py,sha256=1miP4is4FEoci01KSAy2VZVYmlmT5TOCOALBekd2muQ,7211
14
14
  trilogy/core/ergonomics.py,sha256=w3gwXdgrxNHCuaRdyKg73t6F36tj-wIjQf47WZkHmJk,1465
15
15
  trilogy/core/exceptions.py,sha256=NvV_4qLOgKXbpotgRf7c8BANDEvHxlqRPaA53IThQ2o,561
16
- trilogy/core/functions.py,sha256=ARJAyBjeS415-54k3G_bx807rkPZonEulMaLRxSP7vU,10371
16
+ trilogy/core/functions.py,sha256=ShFTStIKbgI-3EZIU0xTumI78AC5QlvARwnBM53P2O0,10677
17
17
  trilogy/core/graph_models.py,sha256=oJUMSpmYhqXlavckHLpR07GJxuQ8dZ1VbB1fB0KaS8c,2036
18
18
  trilogy/core/internal.py,sha256=jNGFHKENnbMiMCtAgsnLZYVSENDK4b5ALecXFZpTDzQ,1075
19
- trilogy/core/models.py,sha256=j55CLTR-vADhDd0va3maMPgNW4fj9GVJ8Kx5S25vu3w,149259
20
- trilogy/core/optimization.py,sha256=7E-Ol51u6ZAxF56F_bzLxgRO-Hu6Yl1ZbPopZJB2tqk,7533
21
- trilogy/core/query_processor.py,sha256=qMVkaK1Lvr9jEftJwAidMdkb_tRx12G07qynEyl91C8,18801
19
+ trilogy/core/models.py,sha256=nSCOXedYRFgYdZa45dputERCvgPRQpRvCyiGglyeM0g,150985
20
+ trilogy/core/optimization.py,sha256=od_60A9F8J8Nj24MHgrxl4vwRwmBFH13TMdoMQvgVKs,7717
21
+ trilogy/core/query_processor.py,sha256=kXuBsIaRHu1s7zB_rAnT_gRe4-VgRSrPE1TnVJXFLtc,16447
22
22
  trilogy/core/optimizations/__init__.py,sha256=bWQecbeiwiDx9LJnLsa7dkWxdbl2wcnkcTN69JyP8iI,356
23
23
  trilogy/core/optimizations/base_optimization.py,sha256=tWWT-xnTbnEU-mNi_isMNbywm8B9WTRsNFwGpeh3rqE,468
24
24
  trilogy/core/optimizations/inline_constant.py,sha256=kHNyc2UoaPVdYfVAPAFwnWuk4sJ_IF5faRtVcDOrBtw,1110
25
25
  trilogy/core/optimizations/inline_datasource.py,sha256=AATzQ6YrtW_1-aQFjQyTYqEYKBoMFhek7ADfBr4uUdQ,3634
26
26
  trilogy/core/optimizations/predicate_pushdown.py,sha256=1l9WnFOSv79e341typG3tTdk0XGl1J_ToQih3LYoGIY,8435
27
27
  trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
- trilogy/core/processing/concept_strategies_v3.py,sha256=ae6FmwiKNiEbOU2GhnzggFMh82MhqxBj9bgr0ituT2w,25633
28
+ trilogy/core/processing/concept_strategies_v3.py,sha256=DO9gybVLku8GEkO3uNPaCeqhalnufsjYYbvDs-gkwNc,35295
29
29
  trilogy/core/processing/graph_utils.py,sha256=aq-kqk4Iado2HywDxWEejWc-7PGO6Oa-ZQLAM6XWPHw,1199
30
- trilogy/core/processing/utility.py,sha256=jFLZmzxHq94q29FInr8XjS5YiqJOPPBYqh8Tlgs432Y,17722
30
+ trilogy/core/processing/utility.py,sha256=v06sqXpnuYct_MMZXxEaiP0WwkeblWpO81QG1Ns3yGc,19420
31
31
  trilogy/core/processing/node_generators/__init__.py,sha256=-mzYkRsaRNa_dfTckYkKVFSR8h8a3ihEiPJDU_tAmDo,672
32
- trilogy/core/processing/node_generators/basic_node.py,sha256=IHj5jEloUe5yojGRLAzt35FcfHqGviWQdS8ETyvr39Q,3292
33
- trilogy/core/processing/node_generators/common.py,sha256=3_Ivrq_wersDZ5pnvyHvsAUc07mRggxRGTiDq47O0Rk,8840
34
- trilogy/core/processing/node_generators/filter_node.py,sha256=gCiv76Cu4idkZRyGkZG44BO50mTqrxYdUSDcZdpd0i4,7724
35
- trilogy/core/processing/node_generators/group_node.py,sha256=G7SrU2X5kjgzeqzzpnPscQBTDcFMc4m7TR6n8VHLC_A,3762
36
- trilogy/core/processing/node_generators/group_to_node.py,sha256=yX0uw6YMxhyWVRMZoMFzEkJe3tB5ByFqrTnuRWVcRh4,2446
37
- trilogy/core/processing/node_generators/multiselect_node.py,sha256=OUjndYjA8xR6yKr-J7R-JxDeYfO6DxmMNNcJiFJzk7g,6138
38
- trilogy/core/processing/node_generators/node_merge_node.py,sha256=D_jsnfoLMrQc08_JvT0wEDvjyzJAxBpdcZFyDN-feV0,13192
39
- trilogy/core/processing/node_generators/rowset_node.py,sha256=tc8jt9bMq_HIdLM24sx_ivc7tTlpucQDEvsC2nkOtrY,4454
40
- trilogy/core/processing/node_generators/select_node.py,sha256=XSMA4kvFdoXlfCpbciXXkbexXkemwUorcAU6P3EwuZY,19843
41
- trilogy/core/processing/node_generators/unnest_node.py,sha256=aZeixbOzMtXi7BPahKr9bOkIhTciyD9Klsj0kZ56F6s,2189
42
- trilogy/core/processing/node_generators/window_node.py,sha256=LSlXe41elFGVRlxRX3MEFimhduGn3o5WE0kLx2JtA4M,3322
43
- trilogy/core/processing/nodes/__init__.py,sha256=jyduHk96j5fpju72sc8swOiBjR3Md866kt8JZGkp3ZU,4866
44
- trilogy/core/processing/nodes/base_node.py,sha256=11Evv2LErwlzCU9ebLWlzldz7VbVMgYiR_sUkVYylKQ,13916
45
- trilogy/core/processing/nodes/filter_node.py,sha256=DBOSGFfkiILrZa1BlLv2uxUSkgWtSIKiZplqyKXPjg8,2132
46
- trilogy/core/processing/nodes/group_node.py,sha256=3zyEs11hv9CoGpO62cUKfClcS58clTUB0IMIkmOV998,6897
47
- trilogy/core/processing/nodes/merge_node.py,sha256=bn7CwZwbYFx-OjNLb9oQuYL_abwAAd_KSSYJGFSEiP8,15022
48
- trilogy/core/processing/nodes/select_node_v2.py,sha256=yoU2PWu-BkiUDECd7V7CKAPjznB_LObpl52HU9Sk5Yc,7433
32
+ trilogy/core/processing/node_generators/basic_node.py,sha256=WQNgJ1MwrMS_BQ-b3XwGGB6eToDykelAVj_fesJuqe0,2069
33
+ trilogy/core/processing/node_generators/common.py,sha256=LwDgPlhWeuw0t07f3kX9IE5LXBdZhXfh-aY0XGk50ak,8946
34
+ trilogy/core/processing/node_generators/filter_node.py,sha256=Vz9Rb67e1dfZgnliekwwLeDPVkthMbdrnrKRdz7J1ik,7654
35
+ trilogy/core/processing/node_generators/group_node.py,sha256=r54IVEhXW-tzod6uEHIQObrxgQt6aNySk5emWkWyqCU,4938
36
+ trilogy/core/processing/node_generators/group_to_node.py,sha256=R9i_wHipxjXJyfYEwfeTw2EPpuanXVA327XyfcP2tBg,2537
37
+ trilogy/core/processing/node_generators/multiselect_node.py,sha256=_KO9lqzHQoy4VAviO0ttQlmK0tjaqrJj4SJPhmoIYm8,6229
38
+ trilogy/core/processing/node_generators/node_merge_node.py,sha256=4aoSkynWYcKAxeN4fU5jnCdxausa5rNgFokoVhPXI80,13511
39
+ trilogy/core/processing/node_generators/rowset_node.py,sha256=gU_ybfYXO9tZqHjUSABIioVpb8AWtITpegj3IGSf2GI,4587
40
+ trilogy/core/processing/node_generators/select_merge_node.py,sha256=MKjlXqFBSin6cTnS6n5lEcNBJsMvSefDIXOwYNVbM0s,10371
41
+ trilogy/core/processing/node_generators/select_node.py,sha256=vUg3gXHGvagdbniIAE7DdqJcQ0V1VAfHtTrw3edYPso,1734
42
+ trilogy/core/processing/node_generators/unnest_node.py,sha256=cZ26CN338CBnd6asML1OBUtNcDzmNlFpY0Vnade4yrc,2256
43
+ trilogy/core/processing/node_generators/window_node.py,sha256=jy3FF8uN0VA7yyrBeR40B9CAqR_5qBP4PiS6Gr-f-7w,2590
44
+ trilogy/core/processing/nodes/__init__.py,sha256=qS5EJDRwwIrCEfS7ibCA2ESE0RPzsAIii1UWd_wNsHA,4760
45
+ trilogy/core/processing/nodes/base_node.py,sha256=sc3HrXkWk-xpsAQ7B7ltX1ZejYAkqFiv8Ei8Jg5VGkQ,15579
46
+ trilogy/core/processing/nodes/filter_node.py,sha256=GfZ9eghpFDI-s7iQP2UqTljCmn25LT_T5TAxDlh7PkQ,2343
47
+ trilogy/core/processing/nodes/group_node.py,sha256=PrBHaGq_f8RmokUw9lXLGJ5YbjdP77P7Ag0pgR6e2cU,7293
48
+ trilogy/core/processing/nodes/merge_node.py,sha256=wlqh0suFfZBJOgkn7vy0OiDs5jza3NCX7eHTEEb6mBQ,14799
49
+ trilogy/core/processing/nodes/select_node_v2.py,sha256=gS9OQgS2TSEK59BQ9R0i83pTHfGJUxv7AkAmT21sYxI,8067
49
50
  trilogy/core/processing/nodes/unnest_node.py,sha256=mAmFluzm2yeeiQ6NfIB7BU_8atRGh-UJfPf9ROwbhr8,2152
50
- trilogy/core/processing/nodes/window_node.py,sha256=X7qxLUKd3tekjUUsmH_4vz5b-U89gMnGd04VBxuu2Ns,1280
51
+ trilogy/core/processing/nodes/window_node.py,sha256=ro0QfMFi4ZmIn5Q4D0M_vJWfnHH_C0MN7XkVkx8Gygg,1214
51
52
  trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
- trilogy/dialect/base.py,sha256=CcLIEqjRYSbxiCQWIjcn0XYb_jXvjY65-SeGAdIFioQ,32888
53
+ trilogy/dialect/base.py,sha256=QDeKbc7vgqfbR649-87cwsOz8G3VY63V19zH6I-WITo,33103
53
54
  trilogy/dialect/bigquery.py,sha256=15KJ-cOpBlk9O7FPviPgmg8xIydJeKx7WfmL3SSsPE8,2953
54
55
  trilogy/dialect/common.py,sha256=Hr0mxcNxjSvhpBM5Wvb_Q7aklAuYj5aBDrW433py0Zs,4403
55
56
  trilogy/dialect/config.py,sha256=tLVEMctaTDhUgARKXUNfHUcIolGaALkQ0RavUvXAY4w,2994
56
- trilogy/dialect/duckdb.py,sha256=u_gpL35kouWxoBLas1h0ABYY2QzlVtEh22hm5h0lCOM,3182
57
+ trilogy/dialect/duckdb.py,sha256=_0a5HBU8zRNtZj7YED3ju4fHXRYG9jNeKwnlZwUDvwI,3419
57
58
  trilogy/dialect/enums.py,sha256=4NdpsydBpDn6jnh0JzFz5VvQEtnShErWtWHVyT6TNpw,3948
58
59
  trilogy/dialect/postgres.py,sha256=ev1RJZsC8BB3vJSxJ4q-TTYqZ4Hk1NXUtuRkLrQEBX0,3254
59
60
  trilogy/dialect/presto.py,sha256=2Rs53UfPxKU0rJTcEbiS-Lxm-CDiqUGojh7yRpQgyRE,3416
60
61
  trilogy/dialect/snowflake.py,sha256=_Bf4XO7-nImMv9XCSsTfVM3g2f_KHdO17VTa9J-HgSM,2989
61
- trilogy/dialect/sql_server.py,sha256=OtXbm1v6NIGyXeC5i18ojUvLeqescc_Pbv3EASUBB94,3074
62
+ trilogy/dialect/sql_server.py,sha256=owUZbMFrooYIMj1DSLstPWxPO7K7WAUEWNvDKM-DMt0,3118
62
63
  trilogy/hooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
63
64
  trilogy/hooks/base_hook.py,sha256=Xkb-A2qCHozYjum0A36zOy5PwTVwrP3NLDF0U2GpgHo,1100
64
65
  trilogy/hooks/graph_hook.py,sha256=onHvMQPwj_KOS3HOTpRFiy7QLLKAiycq2MzJ_Q0Oh5Y,2467
65
- trilogy/hooks/query_debugger.py,sha256=NDChfkPmmW-KINa4TaQmDe_adGiwsKFdGLDSYpbodeU,4282
66
+ trilogy/hooks/query_debugger.py,sha256=Pe-Kw1JGngeLqQOMQb0E3-24jXEavqnPCQ-KOfTfjP8,4357
66
67
  trilogy/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
67
68
  trilogy/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
69
  trilogy/parsing/common.py,sha256=-4LM71ocidA8DI2RngqFEOmhzBrIt8VdBTO4x2BpD8E,9502
69
70
  trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
70
71
  trilogy/parsing/exceptions.py,sha256=92E5i2frv5hj9wxObJZsZqj5T6bglvPzvdvco_vW1Zk,38
71
72
  trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
72
- trilogy/parsing/parse_engine.py,sha256=w-O1UNA7IMeYZPRB-bK1G-qM3nmM_mrQ-eVGSCkyIvM,63265
73
+ trilogy/parsing/parse_engine.py,sha256=r0B7tXAIfTMJ42ZJwP70ulQBrOoM1AAiXFbCQ2rOpQs,64720
73
74
  trilogy/parsing/render.py,sha256=8yxerPAi4AhlhPBlAfbYbOM3F9rz6HzpWVEWPtK2VEg,12321
74
- trilogy/parsing/trilogy.lark,sha256=MFPDmqF0IntySCe7u8ZrLhoyzYYon4dYhnNRdERtEew,11583
75
+ trilogy/parsing/trilogy.lark,sha256=0JAvQBACFNL-X61I0tB_0QPZgsguZgerfHBv903oKh0,11623
75
76
  trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
77
  trilogy/scripts/trilogy.py,sha256=PHxvv6f2ODv0esyyhWxlARgra8dVhqQhYl0lTrSyVNo,3729
77
- pytrilogy-0.0.2.17.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
78
- pytrilogy-0.0.2.17.dist-info/METADATA,sha256=Qy6SlBRVmnn77Iq8QubcjHFBhhRpT0FN16RMXtODzVA,7907
79
- pytrilogy-0.0.2.17.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
80
- pytrilogy-0.0.2.17.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
81
- pytrilogy-0.0.2.17.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
82
- pytrilogy-0.0.2.17.dist-info/RECORD,,
78
+ pytrilogy-0.0.2.19.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
79
+ pytrilogy-0.0.2.19.dist-info/METADATA,sha256=BgEvyZ13aahBi8bULS0lT9Rx5ixjpZ99ZbHeC8mg5Mw,8132
80
+ pytrilogy-0.0.2.19.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
81
+ pytrilogy-0.0.2.19.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
82
+ pytrilogy-0.0.2.19.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
83
+ pytrilogy-0.0.2.19.dist-info/RECORD,,
trilogy/__init__.py CHANGED
@@ -4,6 +4,6 @@ from trilogy.executor import Executor
4
4
  from trilogy.parser import parse
5
5
  from trilogy.constants import CONFIG
6
6
 
7
- __version__ = "0.0.2.17"
7
+ __version__ = "0.0.2.19"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
trilogy/constants.py CHANGED
@@ -24,7 +24,7 @@ class Optimizations:
24
24
  predicate_pushdown: bool = True
25
25
  datasource_inlining: bool = True
26
26
  constant_inlining: bool = True
27
- constant_inline_cutoff: int = 3
27
+ constant_inline_cutoff: int = 10
28
28
  direct_return: bool = True
29
29
 
30
30
 
trilogy/core/enums.py CHANGED
@@ -119,6 +119,7 @@ class FunctionType(Enum):
119
119
  CONSTANT = "constant"
120
120
  COALESCE = "coalesce"
121
121
  IS_NULL = "isnull"
122
+ BOOL = "bool"
122
123
 
123
124
  # COMPLEX
124
125
  INDEX_ACCESS = "index_access"
trilogy/core/functions.py CHANGED
@@ -340,6 +340,17 @@ def IsNull(args: list[Concept]) -> Function:
340
340
  )
341
341
 
342
342
 
343
+ def Bool(args: list[Concept]) -> Function:
344
+ return Function(
345
+ operator=FunctionType.BOOL,
346
+ arguments=args,
347
+ output_datatype=DataType.BOOL,
348
+ output_purpose=function_args_to_output_purpose(args),
349
+ arg_count=1,
350
+ # output_grain=Grain(components=arguments),
351
+ )
352
+
353
+
343
354
  def StrPos(args: list[Concept]) -> Function:
344
355
  return Function(
345
356
  operator=FunctionType.STRPOS,
trilogy/core/models.py CHANGED
@@ -432,6 +432,7 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
432
432
  grain: "Grain" = Field(default=None, validate_default=True)
433
433
  modifiers: Optional[List[Modifier]] = Field(default_factory=list)
434
434
  pseudonyms: Dict[str, Concept] = Field(default_factory=dict)
435
+ _address_cache: str | None = None
435
436
 
436
437
  def __hash__(self):
437
438
  return hash(str(self))
@@ -558,9 +559,19 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
558
559
  grain = ",".join([str(c.address) for c in self.grain.components])
559
560
  return f"{self.namespace}.{self.name}<{grain}>"
560
561
 
561
- @cached_property
562
+ @property
562
563
  def address(self) -> str:
563
- return f"{self.namespace}.{self.name}"
564
+ if not self._address_cache:
565
+ self._address_cache = f"{self.namespace}.{self.name}"
566
+ return self._address_cache
567
+
568
+ @address.setter
569
+ def address(self, address: str) -> None:
570
+ self._address_cache = address
571
+
572
+ def set_name(self, name: str):
573
+ self.name = name
574
+ self.address = f"{self.namespace}.{self.name}"
564
575
 
565
576
  @property
566
577
  def output(self) -> "Concept":
@@ -905,7 +916,13 @@ class Grain(Mergeable, BaseModel):
905
916
 
906
917
  @cached_property
907
918
  def set(self):
908
- return set([c.address for c in self.components_copy])
919
+ base = []
920
+ for x in self.components_copy:
921
+ if x.derivation == PurposeLineage.ROWSET:
922
+ base.append(x.lineage.content.address)
923
+ else:
924
+ base.append(x.address)
925
+ return set(base)
909
926
 
910
927
  def __eq__(self, other: object):
911
928
  if isinstance(other, list):
@@ -1555,6 +1572,10 @@ class OrderBy(Mergeable, Namespaced, BaseModel):
1555
1572
  items=[x.with_merge(source, target, modifiers) for x in self.items]
1556
1573
  )
1557
1574
 
1575
+ @property
1576
+ def concept_arguments(self):
1577
+ return [x.expr for x in self.items]
1578
+
1558
1579
 
1559
1580
  class RawSQLStatement(BaseModel):
1560
1581
  text: str
@@ -1567,6 +1588,13 @@ class SelectStatement(Mergeable, Namespaced, SelectTypeMixin, BaseModel):
1567
1588
  limit: Optional[int] = None
1568
1589
  meta: Optional[Metadata] = Field(default_factory=lambda: Metadata())
1569
1590
 
1591
+ def refresh_bindings(self, environment: Environment):
1592
+ for item in self.selection:
1593
+ if isinstance(item.content, Concept):
1594
+ item.content = environment.concepts[item.content.address].with_grain(
1595
+ self.grain
1596
+ )
1597
+
1570
1598
  def __str__(self):
1571
1599
  from trilogy.parsing.render import render_query
1572
1600
 
@@ -1605,6 +1633,14 @@ class SelectStatement(Mergeable, Namespaced, SelectTypeMixin, BaseModel):
1605
1633
  limit=self.limit,
1606
1634
  )
1607
1635
 
1636
+ @property
1637
+ def locally_derived(self) -> set[str]:
1638
+ locally_derived: set[str] = set()
1639
+ for item in self.selection:
1640
+ if isinstance(item.content, ConceptTransform):
1641
+ locally_derived.add(item.content.output.address)
1642
+ return locally_derived
1643
+
1608
1644
  @property
1609
1645
  def input_components(self) -> List[Concept]:
1610
1646
  output = set()
@@ -1655,11 +1691,22 @@ class SelectStatement(Mergeable, Namespaced, SelectTypeMixin, BaseModel):
1655
1691
  address: Address,
1656
1692
  grain: Grain | None = None,
1657
1693
  ) -> Datasource:
1694
+ if self.where_clause or self.having_clause:
1695
+ modifiers = [Modifier.PARTIAL]
1696
+ else:
1697
+ modifiers = []
1658
1698
  columns = [
1659
1699
  # TODO: replace hardcoded replacement here
1660
- ColumnAssignment(alias=c.address.replace(".", "_"), concept=c)
1700
+ # if the concept is a locally derived concept, it cannot ever be partial
1701
+ # but if it's a concept pulled in from upstream and we have a where clause, it should be partial
1702
+ ColumnAssignment(
1703
+ alias=c.address.replace(".", "_"),
1704
+ concept=c,
1705
+ modifiers=modifiers if c.address not in self.locally_derived else [],
1706
+ )
1661
1707
  for c in self.output_components
1662
1708
  ]
1709
+
1663
1710
  new_datasource = Datasource(
1664
1711
  identifier=identifier,
1665
1712
  address=address,
@@ -1786,6 +1833,10 @@ class MultiSelectStatement(SelectTypeMixin, Mergeable, Namespaced, BaseModel):
1786
1833
  limit: Optional[int] = None
1787
1834
  meta: Optional[Metadata] = Field(default_factory=lambda: Metadata())
1788
1835
 
1836
+ def refresh_bindings(self, environment: Environment):
1837
+ for select in self.selects:
1838
+ select.refresh_bindings(environment)
1839
+
1789
1840
  def __repr__(self):
1790
1841
  return "MultiSelect<" + " MERGE ".join([str(s) for s in self.selects]) + ">"
1791
1842
 
@@ -2255,14 +2306,11 @@ class BaseJoin(BaseModel):
2255
2306
  def __str__(self):
2256
2307
  if self.concept_pairs:
2257
2308
  return (
2258
- f"{self.join_type.value} JOIN {self.left_datasource.identifier} and"
2259
- f" {self.right_datasource.identifier} on"
2309
+ f"{self.join_type.value} on"
2260
2310
  f" {','.join([str(k.left)+'='+str(k.right) for k in self.concept_pairs])}"
2261
2311
  )
2262
2312
  return (
2263
- f"{self.join_type.value} JOIN {self.left_datasource.identifier} and"
2264
- f" {self.right_datasource.identifier} on"
2265
- f" {','.join([str(k) for k in self.concepts])}"
2313
+ f"{self.join_type.value} on" f" {','.join([str(k) for k in self.concepts])}"
2266
2314
  )
2267
2315
 
2268
2316
 
@@ -2395,10 +2443,6 @@ class QueryDatasource(BaseModel):
2395
2443
  raise SyntaxError(
2396
2444
  "Can only merge two query datasources with identical grain"
2397
2445
  )
2398
- if not self.source_type == other.source_type:
2399
- raise SyntaxError(
2400
- "Can only merge two query datasources with identical source type"
2401
- )
2402
2446
  if not self.group_required == other.group_required:
2403
2447
  raise SyntaxError(
2404
2448
  "can only merge two datasources if the group required flag is the same"
@@ -2437,9 +2481,7 @@ class QueryDatasource(BaseModel):
2437
2481
  final_source_map[k] = set(merged_datasources[x.full_name] for x in list(v))
2438
2482
  self_hidden = self.hidden_concepts or []
2439
2483
  other_hidden = other.hidden_concepts or []
2440
- hidden = [
2441
- x for x in self_hidden if x.address in [y.address for y in other_hidden]
2442
- ]
2484
+ hidden = [x for x in self_hidden if x.address in other_hidden]
2443
2485
  qds = QueryDatasource(
2444
2486
  input_concepts=unique(
2445
2487
  self.input_concepts + other.input_concepts, "address"
@@ -2609,7 +2651,7 @@ class CTE(BaseModel):
2609
2651
 
2610
2652
  @property
2611
2653
  def comment(self) -> str:
2612
- base = f"Target: {str(self.grain)}."
2654
+ base = f"Target: {str(self.grain)}. Group: {self.group_to_grain}"
2613
2655
  base += f" Source: {self.source.source_type}."
2614
2656
  if self.parent_ctes:
2615
2657
  base += f" References: {', '.join([x.name for x in self.parent_ctes])}."
@@ -2621,6 +2663,8 @@ class CTE(BaseModel):
2621
2663
  )
2622
2664
  base += f"\n-- Source Map: {self.source_map}."
2623
2665
  base += f"\n-- Output: {', '.join([str(x) for x in self.output_columns])}."
2666
+ if self.source.input_concepts:
2667
+ base += f"\n-- Inputs: {', '.join([str(x) for x in self.source.input_concepts])}."
2624
2668
  if self.hidden_concepts:
2625
2669
  base += f"\n-- Hidden: {', '.join([str(x) for x in self.hidden_concepts])}."
2626
2670
  if self.nullable_concepts:
@@ -2697,7 +2741,7 @@ class CTE(BaseModel):
2697
2741
  raise ValueError(error)
2698
2742
  mutually_hidden = []
2699
2743
  for concept in self.hidden_concepts:
2700
- if concept in other.hidden_concepts:
2744
+ if concept.address in other.hidden_concepts:
2701
2745
  mutually_hidden.append(concept)
2702
2746
  self.partial_concepts = unique(
2703
2747
  self.partial_concepts + other.partial_concepts, "address"
@@ -2797,37 +2841,23 @@ class CTE(BaseModel):
2797
2841
 
2798
2842
  @property
2799
2843
  def group_concepts(self) -> List[Concept]:
2844
+ def check_is_not_in_group(c: Concept):
2845
+ if len(self.source_map.get(c.address, [])) > 0:
2846
+ return False
2847
+ if c.derivation == PurposeLineage.ROWSET:
2848
+ return False
2849
+ if c.derivation == PurposeLineage.CONSTANT:
2850
+ return False
2851
+ if c.purpose == Purpose.METRIC:
2852
+ return True
2853
+ elif c.derivation == PurposeLineage.BASIC and c.lineage:
2854
+ if all([check_is_not_in_group(x) for x in c.lineage.concept_arguments]):
2855
+ return True
2856
+ return False
2857
+
2800
2858
  return (
2801
2859
  unique(
2802
- self.grain.components
2803
- + [
2804
- c
2805
- for c in self.output_columns
2806
- if c.purpose in (Purpose.PROPERTY, Purpose.KEY)
2807
- and c.address not in [x.address for x in self.grain.components]
2808
- ]
2809
- + [
2810
- c
2811
- for c in self.output_columns
2812
- if c.purpose == Purpose.METRIC
2813
- and (
2814
- any(
2815
- [
2816
- c.with_grain(cte.grain) in cte.output_columns
2817
- for cte in self.parent_ctes
2818
- ]
2819
- )
2820
- # if we have this metric from a source
2821
- # it isn't derived here and must be grouped on
2822
- or len(self.source_map[c.address]) > 0
2823
- )
2824
- ]
2825
- + [
2826
- c
2827
- for c in self.output_columns
2828
- if c.purpose == Purpose.CONSTANT
2829
- and self.source_map[c.address] != []
2830
- ],
2860
+ [c for c in self.output_columns if not check_is_not_in_group(c)],
2831
2861
  "address",
2832
2862
  )
2833
2863
  if self.group_to_grain
@@ -3390,6 +3420,18 @@ class Environment(BaseModel):
3390
3420
  ):
3391
3421
 
3392
3422
  self.datasources[datasource.env_label] = datasource
3423
+ for column in datasource.columns:
3424
+ current_concept = column.concept
3425
+ current_derivation = current_concept.derivation
3426
+ if current_derivation not in (PurposeLineage.ROOT, PurposeLineage.CONSTANT):
3427
+ new_concept = current_concept.model_copy(deep=True)
3428
+ new_concept.set_name("_pre_persist_" + current_concept.name)
3429
+ # remove the associated lineage
3430
+ current_concept.lineage = None
3431
+ self.add_concept(new_concept, meta=meta, force=True)
3432
+ self.add_concept(current_concept, meta=meta, force=True)
3433
+ self.merge_concept(new_concept, current_concept, [])
3434
+
3393
3435
  self.gen_concept_list_caches()
3394
3436
  return datasource
3395
3437
 
@@ -125,23 +125,29 @@ def is_direct_return_eligible(cte: CTE) -> CTE | None:
125
125
  for c in cte.source.output_concepts + cte.source.hidden_concepts
126
126
  if c not in cte.source.input_concepts
127
127
  ]
128
- conditions = (
129
- set(x.address for x in direct_parent.condition.concept_arguments)
130
- if direct_parent.condition
131
- else set()
132
- )
128
+
129
+ parent_derived_concepts = [
130
+ c
131
+ for c in direct_parent.source.output_concepts
132
+ + direct_parent.source.hidden_concepts
133
+ if c not in direct_parent.source.input_concepts
134
+ ]
135
+ condition_arguments = cte.condition.row_arguments if cte.condition else []
133
136
  for x in derived_concepts:
134
137
  if x.derivation == PurposeLineage.WINDOW:
135
138
  return None
136
139
  if x.derivation == PurposeLineage.UNNEST:
137
140
  return None
138
141
  if x.derivation == PurposeLineage.AGGREGATE:
139
- if x.address in conditions:
140
- return None
141
- # handling top level nodes that require unpacking
142
- for x in cte.output_columns:
142
+ return None
143
+ for x in parent_derived_concepts:
144
+ if x.address not in condition_arguments:
145
+ continue
143
146
  if x.derivation == PurposeLineage.UNNEST:
144
147
  return None
148
+ if x.derivation == PurposeLineage.WINDOW:
149
+ return None
150
+
145
151
  logger.info(
146
152
  f"[Optimization][EarlyReturn] Removing redundant output CTE with derived_concepts {[x.address for x in derived_concepts]}"
147
153
  )