pytrilogy 0.0.2.15__py3-none-any.whl → 0.0.2.18__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pytrilogy might be problematic. Click here for more details.
- {pytrilogy-0.0.2.15.dist-info → pytrilogy-0.0.2.18.dist-info}/METADATA +12 -8
- pytrilogy-0.0.2.18.dist-info/RECORD +83 -0
- trilogy/__init__.py +1 -1
- trilogy/constants.py +1 -1
- trilogy/core/enums.py +1 -0
- trilogy/core/functions.py +11 -0
- trilogy/core/models.py +105 -59
- trilogy/core/optimization.py +15 -9
- trilogy/core/processing/concept_strategies_v3.py +372 -145
- trilogy/core/processing/node_generators/basic_node.py +27 -55
- trilogy/core/processing/node_generators/common.py +6 -7
- trilogy/core/processing/node_generators/filter_node.py +28 -31
- trilogy/core/processing/node_generators/group_node.py +14 -2
- trilogy/core/processing/node_generators/group_to_node.py +3 -1
- trilogy/core/processing/node_generators/multiselect_node.py +3 -0
- trilogy/core/processing/node_generators/node_merge_node.py +14 -9
- trilogy/core/processing/node_generators/rowset_node.py +12 -12
- trilogy/core/processing/node_generators/select_merge_node.py +302 -0
- trilogy/core/processing/node_generators/select_node.py +7 -511
- trilogy/core/processing/node_generators/unnest_node.py +4 -3
- trilogy/core/processing/node_generators/window_node.py +12 -37
- trilogy/core/processing/nodes/__init__.py +0 -2
- trilogy/core/processing/nodes/base_node.py +69 -20
- trilogy/core/processing/nodes/filter_node.py +3 -0
- trilogy/core/processing/nodes/group_node.py +18 -17
- trilogy/core/processing/nodes/merge_node.py +4 -10
- trilogy/core/processing/nodes/select_node_v2.py +28 -14
- trilogy/core/processing/nodes/window_node.py +1 -2
- trilogy/core/processing/utility.py +51 -3
- trilogy/core/query_processor.py +17 -73
- trilogy/dialect/base.py +8 -3
- trilogy/dialect/common.py +65 -10
- trilogy/dialect/duckdb.py +4 -1
- trilogy/dialect/sql_server.py +3 -3
- trilogy/executor.py +5 -0
- trilogy/hooks/query_debugger.py +5 -3
- trilogy/parsing/parse_engine.py +67 -39
- trilogy/parsing/render.py +2 -0
- trilogy/parsing/trilogy.lark +6 -3
- pytrilogy-0.0.2.15.dist-info/RECORD +0 -82
- {pytrilogy-0.0.2.15.dist-info → pytrilogy-0.0.2.18.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.15.dist-info → pytrilogy-0.0.2.18.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.2.15.dist-info → pytrilogy-0.0.2.18.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.2.15.dist-info → pytrilogy-0.0.2.18.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.
|
|
3
|
+
Version: 0.0.2.18
|
|
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
|
|
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
|
-

|
|
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,
|
|
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)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
trilogy/__init__.py,sha256=NVclSieaZqXKRfCCzUhXoqSrNUdoiMn3ytKw0jCWj7A,291
|
|
2
|
+
trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
trilogy/constants.py,sha256=pZkOneh_65f9Ua6NICu1bHAFAbmQxmiXRXS7tsmCWbQ,1235
|
|
4
|
+
trilogy/engine.py,sha256=R5ubIxYyrxRExz07aZCUfrTsoXCHQ8DKFTDsobXdWdA,1102
|
|
5
|
+
trilogy/executor.py,sha256=An6YLpHQOt96E7ozRQhwZels2hMsDbh0WV767kKCGU0,11294
|
|
6
|
+
trilogy/parser.py,sha256=UtuqSiGiCjpMAYgo1bvNq-b7NSzCA5hzbUW31RXaMII,281
|
|
7
|
+
trilogy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
trilogy/utility.py,sha256=zM__8r29EsyDW7K9VOHz8yvZC2bXFzh7xKy3cL7GKsk,707
|
|
9
|
+
trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
trilogy/core/constants.py,sha256=LL8NLvxb3HRnAjvofyLRXqQJijLcYiXAQYQzGarVD-g,128
|
|
11
|
+
trilogy/core/enums.py,sha256=A9VC0lbP5eo9sndm2TzA-nNJRRmvbjE918ZiEXtcQ_c,6043
|
|
12
|
+
trilogy/core/env_processor.py,sha256=l7TAB0LalxjTYJdTlcmFIkLXuyxa9lrenWLeZfa9qw0,2276
|
|
13
|
+
trilogy/core/environment_helpers.py,sha256=1miP4is4FEoci01KSAy2VZVYmlmT5TOCOALBekd2muQ,7211
|
|
14
|
+
trilogy/core/ergonomics.py,sha256=w3gwXdgrxNHCuaRdyKg73t6F36tj-wIjQf47WZkHmJk,1465
|
|
15
|
+
trilogy/core/exceptions.py,sha256=NvV_4qLOgKXbpotgRf7c8BANDEvHxlqRPaA53IThQ2o,561
|
|
16
|
+
trilogy/core/functions.py,sha256=ShFTStIKbgI-3EZIU0xTumI78AC5QlvARwnBM53P2O0,10677
|
|
17
|
+
trilogy/core/graph_models.py,sha256=oJUMSpmYhqXlavckHLpR07GJxuQ8dZ1VbB1fB0KaS8c,2036
|
|
18
|
+
trilogy/core/internal.py,sha256=jNGFHKENnbMiMCtAgsnLZYVSENDK4b5ALecXFZpTDzQ,1075
|
|
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
|
+
trilogy/core/optimizations/__init__.py,sha256=bWQecbeiwiDx9LJnLsa7dkWxdbl2wcnkcTN69JyP8iI,356
|
|
23
|
+
trilogy/core/optimizations/base_optimization.py,sha256=tWWT-xnTbnEU-mNi_isMNbywm8B9WTRsNFwGpeh3rqE,468
|
|
24
|
+
trilogy/core/optimizations/inline_constant.py,sha256=kHNyc2UoaPVdYfVAPAFwnWuk4sJ_IF5faRtVcDOrBtw,1110
|
|
25
|
+
trilogy/core/optimizations/inline_datasource.py,sha256=AATzQ6YrtW_1-aQFjQyTYqEYKBoMFhek7ADfBr4uUdQ,3634
|
|
26
|
+
trilogy/core/optimizations/predicate_pushdown.py,sha256=1l9WnFOSv79e341typG3tTdk0XGl1J_ToQih3LYoGIY,8435
|
|
27
|
+
trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
|
+
trilogy/core/processing/concept_strategies_v3.py,sha256=DO9gybVLku8GEkO3uNPaCeqhalnufsjYYbvDs-gkwNc,35295
|
|
29
|
+
trilogy/core/processing/graph_utils.py,sha256=aq-kqk4Iado2HywDxWEejWc-7PGO6Oa-ZQLAM6XWPHw,1199
|
|
30
|
+
trilogy/core/processing/utility.py,sha256=plpaAmssWjcxPee8_J4hleazTzQIBL6mBLJ33FKfwOM,19421
|
|
31
|
+
trilogy/core/processing/node_generators/__init__.py,sha256=-mzYkRsaRNa_dfTckYkKVFSR8h8a3ihEiPJDU_tAmDo,672
|
|
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=Dn9vEY-WPFHNN-LtXfgWiHIXspzHDKfkKL5a2KE2gD0,4252
|
|
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=yRDfY8muZN7G2vsdYXF2X1iqbQ2zDUNGlxvSIyKVoWU,13512
|
|
39
|
+
trilogy/core/processing/node_generators/rowset_node.py,sha256=gU_ybfYXO9tZqHjUSABIioVpb8AWtITpegj3IGSf2GI,4587
|
|
40
|
+
trilogy/core/processing/node_generators/select_merge_node.py,sha256=ipSxw1Oqk-hVVGhPhZlvRbptC0Vpwh52hZ7z8oOj2yk,10065
|
|
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
|
|
50
|
+
trilogy/core/processing/nodes/unnest_node.py,sha256=mAmFluzm2yeeiQ6NfIB7BU_8atRGh-UJfPf9ROwbhr8,2152
|
|
51
|
+
trilogy/core/processing/nodes/window_node.py,sha256=ro0QfMFi4ZmIn5Q4D0M_vJWfnHH_C0MN7XkVkx8Gygg,1214
|
|
52
|
+
trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
53
|
+
trilogy/dialect/base.py,sha256=QDeKbc7vgqfbR649-87cwsOz8G3VY63V19zH6I-WITo,33103
|
|
54
|
+
trilogy/dialect/bigquery.py,sha256=15KJ-cOpBlk9O7FPviPgmg8xIydJeKx7WfmL3SSsPE8,2953
|
|
55
|
+
trilogy/dialect/common.py,sha256=Hr0mxcNxjSvhpBM5Wvb_Q7aklAuYj5aBDrW433py0Zs,4403
|
|
56
|
+
trilogy/dialect/config.py,sha256=tLVEMctaTDhUgARKXUNfHUcIolGaALkQ0RavUvXAY4w,2994
|
|
57
|
+
trilogy/dialect/duckdb.py,sha256=_0a5HBU8zRNtZj7YED3ju4fHXRYG9jNeKwnlZwUDvwI,3419
|
|
58
|
+
trilogy/dialect/enums.py,sha256=4NdpsydBpDn6jnh0JzFz5VvQEtnShErWtWHVyT6TNpw,3948
|
|
59
|
+
trilogy/dialect/postgres.py,sha256=ev1RJZsC8BB3vJSxJ4q-TTYqZ4Hk1NXUtuRkLrQEBX0,3254
|
|
60
|
+
trilogy/dialect/presto.py,sha256=2Rs53UfPxKU0rJTcEbiS-Lxm-CDiqUGojh7yRpQgyRE,3416
|
|
61
|
+
trilogy/dialect/snowflake.py,sha256=_Bf4XO7-nImMv9XCSsTfVM3g2f_KHdO17VTa9J-HgSM,2989
|
|
62
|
+
trilogy/dialect/sql_server.py,sha256=owUZbMFrooYIMj1DSLstPWxPO7K7WAUEWNvDKM-DMt0,3118
|
|
63
|
+
trilogy/hooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
64
|
+
trilogy/hooks/base_hook.py,sha256=Xkb-A2qCHozYjum0A36zOy5PwTVwrP3NLDF0U2GpgHo,1100
|
|
65
|
+
trilogy/hooks/graph_hook.py,sha256=onHvMQPwj_KOS3HOTpRFiy7QLLKAiycq2MzJ_Q0Oh5Y,2467
|
|
66
|
+
trilogy/hooks/query_debugger.py,sha256=Pe-Kw1JGngeLqQOMQb0E3-24jXEavqnPCQ-KOfTfjP8,4357
|
|
67
|
+
trilogy/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
68
|
+
trilogy/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
69
|
+
trilogy/parsing/common.py,sha256=-4LM71ocidA8DI2RngqFEOmhzBrIt8VdBTO4x2BpD8E,9502
|
|
70
|
+
trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
|
|
71
|
+
trilogy/parsing/exceptions.py,sha256=92E5i2frv5hj9wxObJZsZqj5T6bglvPzvdvco_vW1Zk,38
|
|
72
|
+
trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
73
|
+
trilogy/parsing/parse_engine.py,sha256=r0B7tXAIfTMJ42ZJwP70ulQBrOoM1AAiXFbCQ2rOpQs,64720
|
|
74
|
+
trilogy/parsing/render.py,sha256=8yxerPAi4AhlhPBlAfbYbOM3F9rz6HzpWVEWPtK2VEg,12321
|
|
75
|
+
trilogy/parsing/trilogy.lark,sha256=0JAvQBACFNL-X61I0tB_0QPZgsguZgerfHBv903oKh0,11623
|
|
76
|
+
trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
77
|
+
trilogy/scripts/trilogy.py,sha256=PHxvv6f2ODv0esyyhWxlARgra8dVhqQhYl0lTrSyVNo,3729
|
|
78
|
+
pytrilogy-0.0.2.18.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
79
|
+
pytrilogy-0.0.2.18.dist-info/METADATA,sha256=erqF5E59Qz6Xr2ZKiBKejkmQUFMGpDxK9NerqOoT6K0,8132
|
|
80
|
+
pytrilogy-0.0.2.18.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
81
|
+
pytrilogy-0.0.2.18.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
|
|
82
|
+
pytrilogy-0.0.2.18.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
83
|
+
pytrilogy-0.0.2.18.dist-info/RECORD,,
|
trilogy/__init__.py
CHANGED
trilogy/constants.py
CHANGED
trilogy/core/enums.py
CHANGED
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
|
-
@
|
|
562
|
+
@property
|
|
562
563
|
def address(self) -> str:
|
|
563
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}
|
|
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}
|
|
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:
|
|
@@ -2660,9 +2704,9 @@ class CTE(BaseModel):
|
|
|
2660
2704
|
if isinstance(join, InstantiatedUnnestJoin):
|
|
2661
2705
|
continue
|
|
2662
2706
|
if join.left_cte.name == parent.name:
|
|
2663
|
-
join.
|
|
2707
|
+
join.inline_cte(parent)
|
|
2664
2708
|
if join.right_cte.name == parent.name:
|
|
2665
|
-
join.
|
|
2709
|
+
join.inline_cte(parent)
|
|
2666
2710
|
for k, v in self.source_map.items():
|
|
2667
2711
|
if isinstance(v, list):
|
|
2668
2712
|
self.source_map[k] = [
|
|
@@ -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.
|
|
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
|
|
@@ -2885,34 +2915,38 @@ class JoinKey(BaseModel):
|
|
|
2885
2915
|
|
|
2886
2916
|
|
|
2887
2917
|
class Join(BaseModel):
|
|
2888
|
-
left_cte: CTE
|
|
2889
|
-
right_cte: CTE
|
|
2918
|
+
left_cte: CTE
|
|
2919
|
+
right_cte: CTE
|
|
2890
2920
|
jointype: JoinType
|
|
2891
2921
|
joinkeys: List[JoinKey]
|
|
2892
2922
|
joinkey_pairs: List[ConceptPair] | None = None
|
|
2923
|
+
inlined_ctes: set[str] = Field(default_factory=set)
|
|
2924
|
+
|
|
2925
|
+
def inline_cte(self, cte: CTE):
|
|
2926
|
+
self.inlined_ctes.add(cte.name)
|
|
2893
2927
|
|
|
2894
2928
|
@property
|
|
2895
2929
|
def left_name(self) -> str:
|
|
2896
|
-
if
|
|
2897
|
-
return self.left_cte.identifier
|
|
2930
|
+
if self.left_cte.name in self.inlined_ctes:
|
|
2931
|
+
return self.left_cte.source.datasources[0].identifier
|
|
2898
2932
|
return self.left_cte.name
|
|
2899
2933
|
|
|
2900
2934
|
@property
|
|
2901
2935
|
def right_name(self) -> str:
|
|
2902
|
-
if
|
|
2903
|
-
return self.right_cte.identifier
|
|
2936
|
+
if self.right_cte.name in self.inlined_ctes:
|
|
2937
|
+
return self.right_cte.source.datasources[0].identifier
|
|
2904
2938
|
return self.right_cte.name
|
|
2905
2939
|
|
|
2906
2940
|
@property
|
|
2907
2941
|
def left_ref(self) -> str:
|
|
2908
|
-
if
|
|
2909
|
-
return f"{self.left_cte.safe_location} as {self.left_cte.identifier}"
|
|
2942
|
+
if self.left_cte.name in self.inlined_ctes:
|
|
2943
|
+
return f"{self.left_cte.source.datasources[0].safe_location} as {self.left_cte.source.datasources[0].identifier}"
|
|
2910
2944
|
return self.left_cte.name
|
|
2911
2945
|
|
|
2912
2946
|
@property
|
|
2913
2947
|
def right_ref(self) -> str:
|
|
2914
|
-
if
|
|
2915
|
-
return f"{self.right_cte.safe_location} as {self.right_cte.identifier}"
|
|
2948
|
+
if self.right_cte.name in self.inlined_ctes:
|
|
2949
|
+
return f"{self.right_cte.source.datasources[0].safe_location} as {self.right_cte.source.datasources[0].identifier}"
|
|
2916
2950
|
return self.right_cte.name
|
|
2917
2951
|
|
|
2918
2952
|
@property
|
|
@@ -3386,6 +3420,18 @@ class Environment(BaseModel):
|
|
|
3386
3420
|
):
|
|
3387
3421
|
|
|
3388
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
|
+
|
|
3389
3435
|
self.gen_concept_list_caches()
|
|
3390
3436
|
return datasource
|
|
3391
3437
|
|
trilogy/core/optimization.py
CHANGED
|
@@ -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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
)
|