pytrilogy 0.0.2.2__py3-none-any.whl → 0.0.2.4__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.2.dist-info → pytrilogy-0.0.2.4.dist-info}/METADATA +3 -3
- {pytrilogy-0.0.2.2.dist-info → pytrilogy-0.0.2.4.dist-info}/RECORD +31 -31
- trilogy/__init__.py +1 -1
- trilogy/core/enums.py +2 -0
- trilogy/core/env_processor.py +5 -0
- trilogy/core/functions.py +39 -6
- trilogy/core/models.py +81 -9
- trilogy/core/optimization.py +46 -31
- trilogy/core/optimizations/predicate_pushdown.py +33 -8
- trilogy/core/processing/concept_strategies_v3.py +10 -0
- trilogy/core/processing/node_generators/basic_node.py +1 -1
- trilogy/core/processing/node_generators/common.py +3 -3
- trilogy/core/processing/node_generators/filter_node.py +20 -16
- trilogy/core/processing/node_generators/node_merge_node.py +46 -108
- trilogy/core/processing/nodes/group_node.py +28 -2
- trilogy/core/processing/utility.py +56 -32
- trilogy/core/query_processor.py +10 -3
- trilogy/dialect/base.py +62 -88
- trilogy/dialect/bigquery.py +3 -1
- trilogy/dialect/duckdb.py +5 -4
- trilogy/dialect/postgres.py +3 -1
- trilogy/dialect/presto.py +4 -1
- trilogy/dialect/snowflake.py +3 -1
- trilogy/dialect/sql_server.py +3 -1
- trilogy/parsing/common.py +5 -1
- trilogy/parsing/parse_engine.py +94 -6
- trilogy/parsing/trilogy.lark +11 -4
- {pytrilogy-0.0.2.2.dist-info → pytrilogy-0.0.2.4.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.2.dist-info → pytrilogy-0.0.2.4.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.2.2.dist-info → pytrilogy-0.0.2.4.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.2.2.dist-info → pytrilogy-0.0.2.4.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.4
|
|
4
4
|
Summary: Declarative, typed query language that compiles to SQL.
|
|
5
5
|
Home-page:
|
|
6
6
|
Author:
|
|
@@ -247,7 +247,7 @@ N/A, only supports default auth. In python you can pass in a custom client.
|
|
|
247
247
|
|
|
248
248
|
|
|
249
249
|
> [!TIP]
|
|
250
|
-
> The CLI can also be used for formatting.
|
|
250
|
+
> The CLI can also be used for formatting. Trilogy has a default formatting style that should always be adhered to. `trilogy fmt <path to trilogy file>`
|
|
251
251
|
|
|
252
252
|
|
|
253
253
|
## More Examples
|
|
@@ -284,7 +284,7 @@ but all are worth checking out. Please open PRs/comment for anything missed!
|
|
|
284
284
|
|
|
285
285
|
#### CONCEPT
|
|
286
286
|
|
|
287
|
-
Types: `string | int | float | bool | date | datetime | time | timestamp | interval`;
|
|
287
|
+
Types: `string | int | float | bool | date | datetime | time | numeric(scale, precision) | timestamp | interval`;
|
|
288
288
|
|
|
289
289
|
Key:
|
|
290
290
|
`key <name> <type>;`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
trilogy/__init__.py,sha256=
|
|
1
|
+
trilogy/__init__.py,sha256=sspl5Npt0npzVRC6wCwiYXa0nK0E7a_biwiL2T4kbjM,290
|
|
2
2
|
trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
trilogy/constants.py,sha256=KIvi-cgU4R9urNgDdGiCsRkCrzjAfM4xGHhZb1SVy2w,881
|
|
4
4
|
trilogy/engine.py,sha256=R5ubIxYyrxRExz07aZCUfrTsoXCHQ8DKFTDsobXdWdA,1102
|
|
@@ -8,34 +8,34 @@ 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=
|
|
12
|
-
trilogy/core/env_processor.py,sha256=
|
|
11
|
+
trilogy/core/enums.py,sha256=ozgw8G0s4CdbL6IUsX5nNT_ZGd4mMkoo8_EGLZXeQYY,5902
|
|
12
|
+
trilogy/core/env_processor.py,sha256=l7TAB0LalxjTYJdTlcmFIkLXuyxa9lrenWLeZfa9qw0,2276
|
|
13
13
|
trilogy/core/environment_helpers.py,sha256=mzBDHhdF9ssZ_-LY8CcaM_ddfJavkpRYrFImUd3cjXI,5972
|
|
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=
|
|
16
|
+
trilogy/core/functions.py,sha256=ARJAyBjeS415-54k3G_bx807rkPZonEulMaLRxSP7vU,10371
|
|
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=
|
|
20
|
-
trilogy/core/optimization.py,sha256=
|
|
21
|
-
trilogy/core/query_processor.py,sha256=
|
|
19
|
+
trilogy/core/models.py,sha256=KVPUfupZVhQxJZCViMP7PELzvNR7WOwkjmqqgEM4oeM,137120
|
|
20
|
+
trilogy/core/optimization.py,sha256=A8S9C9H5RcQcFSQLYtEEBnm-r1CW_e9GEWlLK7q3MqA,4930
|
|
21
|
+
trilogy/core/query_processor.py,sha256=6JiX6YqsTgnsEkIxcmWFxJuM6kRJbXbPGRCQgi8g-B8,17941
|
|
22
22
|
trilogy/core/optimizations/__init__.py,sha256=pxRzNzd2g8oRMy4f_ub5va6bNS2pd4hnyp9JBzTKc1E,300
|
|
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=KiwZ4fnRnSOVK8zzdwMAkafszo3fNn5LY8xgb0X1CbI,3194
|
|
26
|
-
trilogy/core/optimizations/predicate_pushdown.py,sha256=
|
|
26
|
+
trilogy/core/optimizations/predicate_pushdown.py,sha256=iVZV_BVaL3I6wlELPOuJAiuF0heOM7bCUnNqyCxxRVw,6370
|
|
27
27
|
trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
|
-
trilogy/core/processing/concept_strategies_v3.py,sha256=
|
|
28
|
+
trilogy/core/processing/concept_strategies_v3.py,sha256=xCPfg1ySHP6C-u777kqnV8eqIK1X68hqwvRcw61q5X0,23968
|
|
29
29
|
trilogy/core/processing/graph_utils.py,sha256=aq-kqk4Iado2HywDxWEejWc-7PGO6Oa-ZQLAM6XWPHw,1199
|
|
30
|
-
trilogy/core/processing/utility.py,sha256=
|
|
30
|
+
trilogy/core/processing/utility.py,sha256=rhnkn3KgB1MW17mZYSEZ8XFEbC__B7PPmZgpDql4Myg,13614
|
|
31
31
|
trilogy/core/processing/node_generators/__init__.py,sha256=-mzYkRsaRNa_dfTckYkKVFSR8h8a3ihEiPJDU_tAmDo,672
|
|
32
|
-
trilogy/core/processing/node_generators/basic_node.py,sha256=
|
|
33
|
-
trilogy/core/processing/node_generators/common.py,sha256=
|
|
34
|
-
trilogy/core/processing/node_generators/filter_node.py,sha256=
|
|
32
|
+
trilogy/core/processing/node_generators/basic_node.py,sha256=4242PNGTCm2tklqMIkqVu5Iv4m_IeTnOYXxDveuCDZM,2856
|
|
33
|
+
trilogy/core/processing/node_generators/common.py,sha256=uLrqbEsuYREswiTfuTQuxldO9SsS6ylZEQT_q9iB2uY,8948
|
|
34
|
+
trilogy/core/processing/node_generators/filter_node.py,sha256=kVsK9O9QWUgknui3ESHJJCnFY9dVMCHlGpHXn1QtXkU,4824
|
|
35
35
|
trilogy/core/processing/node_generators/group_node.py,sha256=xWI1xNIXEOj6jlRGD9hcv2_vVNvY6lpzJl6pQ8HuFBE,2988
|
|
36
36
|
trilogy/core/processing/node_generators/group_to_node.py,sha256=BzPdYwzoo8gRMH7BDffTTXq4z-mjfCEzvfB5I-P0_nw,2941
|
|
37
37
|
trilogy/core/processing/node_generators/multiselect_node.py,sha256=vP84dnLQy6dtypi6mUbt9sMAcmmrTgQ1Oz4GI6X1IEo,6421
|
|
38
|
-
trilogy/core/processing/node_generators/node_merge_node.py,sha256=
|
|
38
|
+
trilogy/core/processing/node_generators/node_merge_node.py,sha256=JDQEIs43BJCgz0yofztcjd7J20Ybk4W40IJG_6blQJI,12168
|
|
39
39
|
trilogy/core/processing/node_generators/rowset_node.py,sha256=2ROd2c1_o4h5sbnDdaiL1o9UiiwyYw-B6OKyfHYNe0A,6188
|
|
40
40
|
trilogy/core/processing/node_generators/select_node.py,sha256=XGcz74XsWv5POWhV2gGC_jbi7T9g7sY-VtP3sjlppFc,19057
|
|
41
41
|
trilogy/core/processing/node_generators/unnest_node.py,sha256=6CH66eGwpadNX7TzUhWZ8aqIisOtQeHINbLV6X3QBUk,1779
|
|
@@ -43,40 +43,40 @@ trilogy/core/processing/node_generators/window_node.py,sha256=9nXUXUgQrNczU1gaOq
|
|
|
43
43
|
trilogy/core/processing/nodes/__init__.py,sha256=-8ONG7Nqa6sNLpst5wNNyMigyaHRfUKGdE3xce8E3mM,3998
|
|
44
44
|
trilogy/core/processing/nodes/base_node.py,sha256=q1G4FO3Q4OuboWL8EaJgJ4jcnnqM9QsothMIcF2ot1g,11161
|
|
45
45
|
trilogy/core/processing/nodes/filter_node.py,sha256=ImUHYj5G5eHbOtki-ZMR31Op38wKOjR1xu_ETiE_D6I,1932
|
|
46
|
-
trilogy/core/processing/nodes/group_node.py,sha256=
|
|
46
|
+
trilogy/core/processing/nodes/group_node.py,sha256=j3vtFQIn_t4EfCke8dGULlwVkG2KKPYfgGwo2rvgcww,6322
|
|
47
47
|
trilogy/core/processing/nodes/merge_node.py,sha256=W7ryL9rceylCQGNgcuGY-dSiNoIz8qrbpJXKzg_Niwo,14056
|
|
48
48
|
trilogy/core/processing/nodes/select_node_v2.py,sha256=COu-WPuyabGAc3HTkJB-_7eNzR4OgxIL4YDuZIRrST0,7179
|
|
49
49
|
trilogy/core/processing/nodes/unnest_node.py,sha256=JFtm90IVM-46aCYkTNIaJah6v9ApAfonjVhcVM1HmDE,1903
|
|
50
50
|
trilogy/core/processing/nodes/window_node.py,sha256=X7qxLUKd3tekjUUsmH_4vz5b-U89gMnGd04VBxuu2Ns,1280
|
|
51
51
|
trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
52
|
-
trilogy/dialect/base.py,sha256=
|
|
53
|
-
trilogy/dialect/bigquery.py,sha256=
|
|
52
|
+
trilogy/dialect/base.py,sha256=aE8iJJhkGtZE04mYm_wQx3ebQKYbcRsKISUd9mbrZ2A,28782
|
|
53
|
+
trilogy/dialect/bigquery.py,sha256=15KJ-cOpBlk9O7FPviPgmg8xIydJeKx7WfmL3SSsPE8,2953
|
|
54
54
|
trilogy/dialect/common.py,sha256=5jdOHWIj3Xv8F8y5mnyWHLjxD_we2fncM-ZnNetJP7U,2781
|
|
55
55
|
trilogy/dialect/config.py,sha256=tLVEMctaTDhUgARKXUNfHUcIolGaALkQ0RavUvXAY4w,2994
|
|
56
|
-
trilogy/dialect/duckdb.py,sha256=
|
|
56
|
+
trilogy/dialect/duckdb.py,sha256=JEdhctDxZr-FEodot5V4De6JnrQDJT02zbjX48j6ay8,3125
|
|
57
57
|
trilogy/dialect/enums.py,sha256=4NdpsydBpDn6jnh0JzFz5VvQEtnShErWtWHVyT6TNpw,3948
|
|
58
|
-
trilogy/dialect/postgres.py,sha256=
|
|
59
|
-
trilogy/dialect/presto.py,sha256=
|
|
60
|
-
trilogy/dialect/snowflake.py,sha256=
|
|
61
|
-
trilogy/dialect/sql_server.py,sha256=
|
|
58
|
+
trilogy/dialect/postgres.py,sha256=ev1RJZsC8BB3vJSxJ4q-TTYqZ4Hk1NXUtuRkLrQEBX0,3254
|
|
59
|
+
trilogy/dialect/presto.py,sha256=InvHDXsGLDG3E3ffl7hGZ-9y3vH8RO6UhNE0OSra01g,3269
|
|
60
|
+
trilogy/dialect/snowflake.py,sha256=_Bf4XO7-nImMv9XCSsTfVM3g2f_KHdO17VTa9J-HgSM,2989
|
|
61
|
+
trilogy/dialect/sql_server.py,sha256=OtXbm1v6NIGyXeC5i18ojUvLeqescc_Pbv3EASUBB94,3074
|
|
62
62
|
trilogy/hooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
63
63
|
trilogy/hooks/base_hook.py,sha256=Xkb-A2qCHozYjum0A36zOy5PwTVwrP3NLDF0U2GpgHo,1100
|
|
64
64
|
trilogy/hooks/graph_hook.py,sha256=onHvMQPwj_KOS3HOTpRFiy7QLLKAiycq2MzJ_Q0Oh5Y,2467
|
|
65
65
|
trilogy/hooks/query_debugger.py,sha256=NDChfkPmmW-KINa4TaQmDe_adGiwsKFdGLDSYpbodeU,4282
|
|
66
66
|
trilogy/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
67
67
|
trilogy/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
68
|
-
trilogy/parsing/common.py,sha256=
|
|
68
|
+
trilogy/parsing/common.py,sha256=aYmLtsEk8VJj5yYASzUcTf0i0lXVsZBbZJwphwokxK4,6092
|
|
69
69
|
trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
|
|
70
70
|
trilogy/parsing/exceptions.py,sha256=92E5i2frv5hj9wxObJZsZqj5T6bglvPzvdvco_vW1Zk,38
|
|
71
71
|
trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
72
|
-
trilogy/parsing/parse_engine.py,sha256=
|
|
72
|
+
trilogy/parsing/parse_engine.py,sha256=_Cpq7bq6QyuDcnmYX_fDLGNFNSQPj173cqYsh3V6jH8,60969
|
|
73
73
|
trilogy/parsing/render.py,sha256=Gy_6wVYPwYLf35Iota08sbqveuWILtUhI8MYStcvtJM,12174
|
|
74
|
-
trilogy/parsing/trilogy.lark,sha256=
|
|
74
|
+
trilogy/parsing/trilogy.lark,sha256=uQlyb4RQ6Tpp8lRlXLK_Qb8sD1qzYVYErRI97v22Zak,11317
|
|
75
75
|
trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
76
76
|
trilogy/scripts/trilogy.py,sha256=PHxvv6f2ODv0esyyhWxlARgra8dVhqQhYl0lTrSyVNo,3729
|
|
77
|
-
pytrilogy-0.0.2.
|
|
78
|
-
pytrilogy-0.0.2.
|
|
79
|
-
pytrilogy-0.0.2.
|
|
80
|
-
pytrilogy-0.0.2.
|
|
81
|
-
pytrilogy-0.0.2.
|
|
82
|
-
pytrilogy-0.0.2.
|
|
77
|
+
pytrilogy-0.0.2.4.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
78
|
+
pytrilogy-0.0.2.4.dist-info/METADATA,sha256=R3D4ULv2Y2yJRO0sToJ5FlPPiqkU8H02X035M-vaG9s,7906
|
|
79
|
+
pytrilogy-0.0.2.4.dist-info/WHEEL,sha256=UvcQYKBHoFqaQd6LKyqHw9fxEolWLQnlzP0h_LgJAfI,91
|
|
80
|
+
pytrilogy-0.0.2.4.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
|
|
81
|
+
pytrilogy-0.0.2.4.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
82
|
+
pytrilogy-0.0.2.4.dist-info/RECORD,,
|
trilogy/__init__.py
CHANGED
trilogy/core/enums.py
CHANGED
trilogy/core/env_processor.py
CHANGED
|
@@ -17,6 +17,11 @@ def add_concept(concept: Concept, g: ReferenceGraph):
|
|
|
17
17
|
for _, pseudonym in concept.pseudonyms.items():
|
|
18
18
|
pseudonym = pseudonym.with_default_grain()
|
|
19
19
|
pseudonym_node = concept_to_node(pseudonym)
|
|
20
|
+
if (pseudonym_node, node_name) in g.edges and (
|
|
21
|
+
node_name,
|
|
22
|
+
pseudonym_node,
|
|
23
|
+
) in g.edges:
|
|
24
|
+
continue
|
|
20
25
|
if pseudonym_node.split("@")[0] == node_name.split("@")[0]:
|
|
21
26
|
continue
|
|
22
27
|
g.add_edge(pseudonym_node, node_name, pseudonym=True)
|
trilogy/core/functions.py
CHANGED
|
@@ -202,15 +202,21 @@ def Split(args: list[Concept]) -> Function:
|
|
|
202
202
|
)
|
|
203
203
|
|
|
204
204
|
|
|
205
|
+
def get_index_output_type(
|
|
206
|
+
arg: Concept,
|
|
207
|
+
) -> DataType | StructType | MapType | ListType | NumericType:
|
|
208
|
+
if isinstance(arg.datatype, ListType):
|
|
209
|
+
return arg.datatype.value_data_type
|
|
210
|
+
elif isinstance(arg.datatype, MapType):
|
|
211
|
+
return arg.datatype.value_data_type
|
|
212
|
+
return arg.datatype
|
|
213
|
+
|
|
214
|
+
|
|
205
215
|
def IndexAccess(args: list[Concept]):
|
|
206
216
|
return Function(
|
|
207
217
|
operator=FunctionType.INDEX_ACCESS,
|
|
208
218
|
arguments=args,
|
|
209
|
-
output_datatype=(
|
|
210
|
-
args[0].datatype.value_data_type
|
|
211
|
-
if isinstance(args[0].datatype, ListType)
|
|
212
|
-
else args[0].datatype
|
|
213
|
-
),
|
|
219
|
+
output_datatype=get_index_output_type(args[0]),
|
|
214
220
|
output_purpose=Purpose.PROPERTY,
|
|
215
221
|
valid_inputs=[
|
|
216
222
|
{
|
|
@@ -226,11 +232,38 @@ def IndexAccess(args: list[Concept]):
|
|
|
226
232
|
)
|
|
227
233
|
|
|
228
234
|
|
|
235
|
+
def MapAccess(args: list[Concept]):
|
|
236
|
+
return Function(
|
|
237
|
+
operator=FunctionType.MAP_ACCESS,
|
|
238
|
+
arguments=args,
|
|
239
|
+
output_datatype=get_index_output_type(args[0]),
|
|
240
|
+
output_purpose=Purpose.PROPERTY,
|
|
241
|
+
valid_inputs=[
|
|
242
|
+
{
|
|
243
|
+
DataType.MAP,
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
DataType.INTEGER,
|
|
247
|
+
DataType.STRING,
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
arg_count=2,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def get_attr_datatype(
|
|
255
|
+
arg: Concept, lookup
|
|
256
|
+
) -> DataType | ListType | StructType | MapType | NumericType:
|
|
257
|
+
if isinstance(arg.datatype, StructType):
|
|
258
|
+
return arg_to_datatype(arg.datatype.fields_map[lookup])
|
|
259
|
+
return arg.datatype
|
|
260
|
+
|
|
261
|
+
|
|
229
262
|
def AttrAccess(args: list[Concept]):
|
|
230
263
|
return Function(
|
|
231
264
|
operator=FunctionType.ATTR_ACCESS,
|
|
232
265
|
arguments=args,
|
|
233
|
-
output_datatype=args[0]
|
|
266
|
+
output_datatype=get_attr_datatype(args[0], args[1]), # type: ignore
|
|
234
267
|
output_purpose=Purpose.PROPERTY,
|
|
235
268
|
valid_inputs=[
|
|
236
269
|
{DataType.STRUCT},
|
trilogy/core/models.py
CHANGED
|
@@ -67,7 +67,7 @@ from trilogy.core.enums import (
|
|
|
67
67
|
)
|
|
68
68
|
from trilogy.core.exceptions import UndefinedConceptException, InvalidSyntaxException
|
|
69
69
|
from trilogy.utility import unique
|
|
70
|
-
from collections import UserList
|
|
70
|
+
from collections import UserList, UserDict
|
|
71
71
|
from functools import cached_property
|
|
72
72
|
from abc import ABC
|
|
73
73
|
|
|
@@ -267,7 +267,7 @@ class ListType(BaseModel):
|
|
|
267
267
|
|
|
268
268
|
class MapType(BaseModel):
|
|
269
269
|
key_type: DataType
|
|
270
|
-
|
|
270
|
+
value_type: ALL_TYPES
|
|
271
271
|
|
|
272
272
|
@property
|
|
273
273
|
def data_type(self):
|
|
@@ -277,10 +277,26 @@ class MapType(BaseModel):
|
|
|
277
277
|
def value(self):
|
|
278
278
|
return self.data_type.value
|
|
279
279
|
|
|
280
|
+
@property
|
|
281
|
+
def value_data_type(
|
|
282
|
+
self,
|
|
283
|
+
) -> DataType | StructType | MapType | ListType | NumericType:
|
|
284
|
+
if isinstance(self.value_type, Concept):
|
|
285
|
+
return self.value_type.datatype
|
|
286
|
+
return self.value_type
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def key_data_type(
|
|
290
|
+
self,
|
|
291
|
+
) -> DataType | StructType | MapType | ListType | NumericType:
|
|
292
|
+
if isinstance(self.key_type, Concept):
|
|
293
|
+
return self.key_type.datatype
|
|
294
|
+
return self.key_type
|
|
295
|
+
|
|
280
296
|
|
|
281
297
|
class StructType(BaseModel):
|
|
282
298
|
fields: List[ALL_TYPES]
|
|
283
|
-
fields_map: Dict[str, Concept] = Field(default_factory=dict)
|
|
299
|
+
fields_map: Dict[str, Concept | int | float | str] = Field(default_factory=dict)
|
|
284
300
|
|
|
285
301
|
@property
|
|
286
302
|
def data_type(self):
|
|
@@ -314,6 +330,34 @@ class ListWrapper(Generic[VT], UserList):
|
|
|
314
330
|
return cls(v, type=arg_to_datatype(v[0]))
|
|
315
331
|
|
|
316
332
|
|
|
333
|
+
class MapWrapper(Generic[KT, VT], UserDict):
|
|
334
|
+
"""Used to distinguish parsed map objects from other dicts"""
|
|
335
|
+
|
|
336
|
+
def __init__(self, *args, key_type: DataType, value_type: DataType, **kwargs):
|
|
337
|
+
super().__init__(*args, **kwargs)
|
|
338
|
+
self.key_type = key_type
|
|
339
|
+
self.value_type = value_type
|
|
340
|
+
|
|
341
|
+
@classmethod
|
|
342
|
+
def __get_pydantic_core_schema__(
|
|
343
|
+
cls, source_type: Any, handler: Callable[[Any], core_schema.CoreSchema]
|
|
344
|
+
) -> core_schema.CoreSchema:
|
|
345
|
+
args = get_args(source_type)
|
|
346
|
+
if args:
|
|
347
|
+
schema = handler(Dict[args]) # type: ignore
|
|
348
|
+
else:
|
|
349
|
+
schema = handler(Dict)
|
|
350
|
+
return core_schema.no_info_after_validator_function(cls.validate, schema)
|
|
351
|
+
|
|
352
|
+
@classmethod
|
|
353
|
+
def validate(cls, v):
|
|
354
|
+
return cls(
|
|
355
|
+
v,
|
|
356
|
+
key_type=arg_to_datatype(list(v.keys()).pop()),
|
|
357
|
+
value_type=arg_to_datatype(list(v.values()).pop()),
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
|
|
317
361
|
class Metadata(BaseModel):
|
|
318
362
|
"""Metadata container object.
|
|
319
363
|
TODO: support arbitrary tags"""
|
|
@@ -375,6 +419,23 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
375
419
|
def __hash__(self):
|
|
376
420
|
return hash(str(self))
|
|
377
421
|
|
|
422
|
+
@property
|
|
423
|
+
def is_aggregate(self):
|
|
424
|
+
if (
|
|
425
|
+
self.lineage
|
|
426
|
+
and isinstance(self.lineage, Function)
|
|
427
|
+
and self.lineage.operator in FunctionClass.AGGREGATE_FUNCTIONS.value
|
|
428
|
+
):
|
|
429
|
+
return True
|
|
430
|
+
if (
|
|
431
|
+
self.lineage
|
|
432
|
+
and isinstance(self.lineage, AggregateWrapper)
|
|
433
|
+
and self.lineage.function.operator
|
|
434
|
+
in FunctionClass.AGGREGATE_FUNCTIONS.value
|
|
435
|
+
):
|
|
436
|
+
return True
|
|
437
|
+
return False
|
|
438
|
+
|
|
378
439
|
def with_merge(self, source: Concept, target: Concept, modifiers: List[Modifier]):
|
|
379
440
|
if self.address == source.address:
|
|
380
441
|
new = target.with_grain(self.grain.with_merge(source, target, modifiers))
|
|
@@ -949,8 +1010,8 @@ class Function(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
949
1010
|
output_purpose: Purpose
|
|
950
1011
|
valid_inputs: Optional[
|
|
951
1012
|
Union[
|
|
952
|
-
Set[DataType | ListType | StructType | NumericType],
|
|
953
|
-
List[Set[DataType | ListType | StructType
|
|
1013
|
+
Set[DataType | ListType | StructType | MapType | NumericType],
|
|
1014
|
+
List[Set[DataType | ListType | StructType | MapType | NumericType]],
|
|
954
1015
|
]
|
|
955
1016
|
] = None
|
|
956
1017
|
arguments: Sequence[
|
|
@@ -961,17 +1022,17 @@ class Function(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
961
1022
|
int,
|
|
962
1023
|
float,
|
|
963
1024
|
str,
|
|
1025
|
+
MapWrapper[Any, Any],
|
|
964
1026
|
DataType,
|
|
965
1027
|
ListType,
|
|
1028
|
+
MapType,
|
|
966
1029
|
NumericType,
|
|
967
1030
|
DatePart,
|
|
968
1031
|
"Parenthetical",
|
|
969
1032
|
CaseWhen,
|
|
970
1033
|
"CaseElse",
|
|
971
1034
|
list,
|
|
972
|
-
ListWrapper[
|
|
973
|
-
ListWrapper[str],
|
|
974
|
-
ListWrapper[float],
|
|
1035
|
+
ListWrapper[Any],
|
|
975
1036
|
]
|
|
976
1037
|
]
|
|
977
1038
|
|
|
@@ -2342,7 +2403,6 @@ class CTE(BaseModel):
|
|
|
2342
2403
|
hidden_concepts: List[Concept] = Field(default_factory=list)
|
|
2343
2404
|
order_by: Optional[OrderBy] = None
|
|
2344
2405
|
limit: Optional[int] = None
|
|
2345
|
-
requires_nesting: bool = True
|
|
2346
2406
|
base_name_override: Optional[str] = None
|
|
2347
2407
|
base_alias_override: Optional[str] = None
|
|
2348
2408
|
|
|
@@ -3141,6 +3201,7 @@ class Environment(BaseModel):
|
|
|
3141
3201
|
v.pseudonyms[source.address] = source
|
|
3142
3202
|
if v.address == source.address:
|
|
3143
3203
|
replacements[k] = target
|
|
3204
|
+
v.pseudonyms[target.address] = target
|
|
3144
3205
|
self.concepts.update(replacements)
|
|
3145
3206
|
|
|
3146
3207
|
for k, ds in self.datasources.items():
|
|
@@ -4115,6 +4176,15 @@ def list_to_wrapper(args):
|
|
|
4115
4176
|
return ListWrapper(args, type=types[0])
|
|
4116
4177
|
|
|
4117
4178
|
|
|
4179
|
+
def dict_to_map_wrapper(arg):
|
|
4180
|
+
key_types = [arg_to_datatype(arg) for arg in arg.keys()]
|
|
4181
|
+
|
|
4182
|
+
value_types = [arg_to_datatype(arg) for arg in arg.values()]
|
|
4183
|
+
assert len(set(key_types)) == 1
|
|
4184
|
+
assert len(set(key_types)) == 1
|
|
4185
|
+
return MapWrapper(arg, key_type=key_types[0], value_type=value_types[0])
|
|
4186
|
+
|
|
4187
|
+
|
|
4118
4188
|
def arg_to_datatype(arg) -> DataType | ListType | StructType | MapType | NumericType:
|
|
4119
4189
|
if isinstance(arg, Function):
|
|
4120
4190
|
return arg.output_datatype
|
|
@@ -4143,5 +4213,7 @@ def arg_to_datatype(arg) -> DataType | ListType | StructType | MapType | Numeric
|
|
|
4143
4213
|
elif isinstance(arg, list):
|
|
4144
4214
|
wrapper = list_to_wrapper(arg)
|
|
4145
4215
|
return ListType(type=wrapper.type)
|
|
4216
|
+
elif isinstance(arg, MapWrapper):
|
|
4217
|
+
return MapType(key_type=arg.key_type, value_type=arg.value_type)
|
|
4146
4218
|
else:
|
|
4147
4219
|
raise ValueError(f"Cannot parse arg datatype for arg of raw type {type(arg)}")
|
trilogy/core/optimization.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from trilogy.core.models import (
|
|
2
2
|
CTE,
|
|
3
3
|
SelectStatement,
|
|
4
|
-
PersistStatement,
|
|
5
4
|
MultiSelectStatement,
|
|
5
|
+
Conditional,
|
|
6
6
|
)
|
|
7
|
-
from trilogy.core.enums import PurposeLineage
|
|
7
|
+
from trilogy.core.enums import PurposeLineage, BooleanOperator
|
|
8
8
|
from trilogy.constants import logger, CONFIG
|
|
9
9
|
from trilogy.core.optimizations import (
|
|
10
10
|
OptimizationRule,
|
|
@@ -42,34 +42,45 @@ def gen_inverse_map(input: list[CTE]) -> dict[str, list[CTE]]:
|
|
|
42
42
|
return inverse_map
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
def is_direct_return_eligible(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if
|
|
49
|
-
return
|
|
45
|
+
def is_direct_return_eligible(cte: CTE) -> CTE | None:
|
|
46
|
+
# if isinstance(select, (PersistStatement, MultiSelectStatement)):
|
|
47
|
+
# return False
|
|
48
|
+
if len(cte.parent_ctes) != 1:
|
|
49
|
+
return None
|
|
50
|
+
direct_parent = cte.parent_ctes[0]
|
|
51
|
+
|
|
52
|
+
output_addresses = set([x.address for x in cte.output_columns])
|
|
53
|
+
parent_output_addresses = set([x.address for x in direct_parent.output_columns])
|
|
54
|
+
if not output_addresses.issubset(parent_output_addresses):
|
|
55
|
+
return None
|
|
56
|
+
if not direct_parent.grain == cte.grain:
|
|
57
|
+
return None
|
|
50
58
|
derived_concepts = [
|
|
51
59
|
c
|
|
52
60
|
for c in cte.source.output_concepts + cte.source.hidden_concepts
|
|
53
61
|
if c not in cte.source.input_concepts
|
|
54
62
|
]
|
|
55
|
-
eligible = True
|
|
56
63
|
conditions = (
|
|
57
|
-
set(x.address for x in
|
|
58
|
-
if
|
|
64
|
+
set(x.address for x in direct_parent.condition.concept_arguments)
|
|
65
|
+
if direct_parent.condition
|
|
59
66
|
else set()
|
|
60
67
|
)
|
|
61
68
|
for x in derived_concepts:
|
|
62
69
|
if x.derivation == PurposeLineage.WINDOW:
|
|
63
|
-
return
|
|
70
|
+
return None
|
|
64
71
|
if x.derivation == PurposeLineage.UNNEST:
|
|
65
|
-
return
|
|
72
|
+
return None
|
|
66
73
|
if x.derivation == PurposeLineage.AGGREGATE:
|
|
67
74
|
if x.address in conditions:
|
|
68
|
-
return
|
|
75
|
+
return None
|
|
76
|
+
# handling top level nodes that require unpacking
|
|
77
|
+
for x in cte.output_columns:
|
|
78
|
+
if x.derivation == PurposeLineage.UNNEST:
|
|
79
|
+
return None
|
|
69
80
|
logger.info(
|
|
70
|
-
f"[Optimization][EarlyReturn]
|
|
81
|
+
f"[Optimization][EarlyReturn] Removing redundant output CTE with derived_concepts {[x.address for x in derived_concepts]}"
|
|
71
82
|
)
|
|
72
|
-
return
|
|
83
|
+
return direct_parent
|
|
73
84
|
|
|
74
85
|
|
|
75
86
|
def sort_select_output(cte: CTE, query: SelectStatement | MultiSelectStatement):
|
|
@@ -90,23 +101,27 @@ def optimize_ctes(
|
|
|
90
101
|
input: list[CTE], root_cte: CTE, select: SelectStatement | MultiSelectStatement
|
|
91
102
|
) -> list[CTE]:
|
|
92
103
|
|
|
93
|
-
|
|
94
|
-
|
|
104
|
+
direct_parent: CTE | None = root_cte
|
|
105
|
+
while CONFIG.optimizations.direct_return and (
|
|
106
|
+
direct_parent := is_direct_return_eligible(root_cte)
|
|
95
107
|
):
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
direct_parent.order_by = root_cte.order_by
|
|
109
|
+
direct_parent.limit = root_cte.limit
|
|
110
|
+
direct_parent.hidden_concepts = (
|
|
111
|
+
root_cte.hidden_concepts + direct_parent.hidden_concepts
|
|
112
|
+
)
|
|
113
|
+
if root_cte.condition:
|
|
114
|
+
if direct_parent.condition:
|
|
115
|
+
direct_parent.condition = Conditional(
|
|
116
|
+
left=direct_parent.condition,
|
|
117
|
+
operator=BooleanOperator.AND,
|
|
118
|
+
right=root_cte.condition,
|
|
119
|
+
)
|
|
120
|
+
else:
|
|
121
|
+
direct_parent.condition = root_cte.condition
|
|
122
|
+
root_cte = direct_parent
|
|
123
|
+
|
|
124
|
+
sort_select_output(root_cte, select)
|
|
110
125
|
|
|
111
126
|
REGISTERED_RULES: list["OptimizationRule"] = []
|
|
112
127
|
if CONFIG.optimizations.constant_inlining:
|
|
@@ -3,18 +3,36 @@ from trilogy.core.models import (
|
|
|
3
3
|
Conditional,
|
|
4
4
|
BooleanOperator,
|
|
5
5
|
Datasource,
|
|
6
|
+
SubselectComparison,
|
|
7
|
+
Comparison,
|
|
8
|
+
Parenthetical,
|
|
6
9
|
)
|
|
7
10
|
from trilogy.core.optimizations.base_optimization import OptimizationRule
|
|
11
|
+
from trilogy.core.processing.utility import is_scalar_condition
|
|
8
12
|
|
|
9
13
|
|
|
10
|
-
def decompose_condition(
|
|
11
|
-
|
|
14
|
+
def decompose_condition(
|
|
15
|
+
conditional: Conditional,
|
|
16
|
+
) -> list[SubselectComparison | Comparison | Conditional | Parenthetical]:
|
|
17
|
+
chunks: list[SubselectComparison | Comparison | Conditional | Parenthetical] = []
|
|
12
18
|
if conditional.operator == BooleanOperator.AND:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
if not (
|
|
20
|
+
isinstance(
|
|
21
|
+
conditional.left,
|
|
22
|
+
(SubselectComparison, Comparison, Conditional, Parenthetical),
|
|
23
|
+
)
|
|
24
|
+
and isinstance(
|
|
25
|
+
conditional.right,
|
|
26
|
+
(SubselectComparison, Comparison, Conditional, Parenthetical),
|
|
27
|
+
)
|
|
28
|
+
):
|
|
29
|
+
chunks.append(conditional)
|
|
30
|
+
else:
|
|
31
|
+
for val in [conditional.left, conditional.right]:
|
|
32
|
+
if isinstance(val, Conditional):
|
|
33
|
+
chunks.extend(decompose_condition(val))
|
|
34
|
+
else:
|
|
35
|
+
chunks.append(val)
|
|
18
36
|
else:
|
|
19
37
|
chunks.append(conditional)
|
|
20
38
|
return chunks
|
|
@@ -128,7 +146,14 @@ class PredicatePushdown(OptimizationRule):
|
|
|
128
146
|
)
|
|
129
147
|
optimized = False
|
|
130
148
|
for candidate in candidates:
|
|
131
|
-
|
|
149
|
+
if not is_scalar_condition(candidate):
|
|
150
|
+
self.debug(
|
|
151
|
+
f"Skipping {candidate} as not a basic [no aggregate, etc] condition"
|
|
152
|
+
)
|
|
153
|
+
continue
|
|
154
|
+
self.log(
|
|
155
|
+
f"Checking candidate {candidate}, {type(candidate)}, scalar: {is_scalar_condition(candidate)}"
|
|
156
|
+
)
|
|
132
157
|
for parent_cte in cte.parent_ctes:
|
|
133
158
|
local_pushdown = self._check_parent(
|
|
134
159
|
parent_cte=parent_cte, candidate=candidate, inverse_map=inverse_map
|
|
@@ -336,8 +336,11 @@ def validate_concept(
|
|
|
336
336
|
virtual_addresses: set[str],
|
|
337
337
|
found_map: dict[str, set[Concept]],
|
|
338
338
|
accept_partial: bool,
|
|
339
|
+
seen: set[str],
|
|
339
340
|
):
|
|
341
|
+
|
|
340
342
|
found_map[str(node)].add(concept)
|
|
343
|
+
seen.add(concept.address)
|
|
341
344
|
if concept not in node.partial_concepts:
|
|
342
345
|
|
|
343
346
|
found_addresses.add(concept.address)
|
|
@@ -357,6 +360,8 @@ def validate_concept(
|
|
|
357
360
|
for _, v in concept.pseudonyms.items():
|
|
358
361
|
if v.address == concept.address:
|
|
359
362
|
return
|
|
363
|
+
if v.address in seen:
|
|
364
|
+
return
|
|
360
365
|
validate_concept(
|
|
361
366
|
v,
|
|
362
367
|
node,
|
|
@@ -366,6 +371,7 @@ def validate_concept(
|
|
|
366
371
|
virtual_addresses,
|
|
367
372
|
found_map,
|
|
368
373
|
accept_partial,
|
|
374
|
+
seen=seen,
|
|
369
375
|
)
|
|
370
376
|
|
|
371
377
|
|
|
@@ -379,8 +385,10 @@ def validate_stack(
|
|
|
379
385
|
non_partial_addresses: set[str] = set()
|
|
380
386
|
partial_addresses: set[str] = set()
|
|
381
387
|
virtual_addresses: set[str] = set()
|
|
388
|
+
seen: set[str] = set()
|
|
382
389
|
for node in stack:
|
|
383
390
|
resolved = node.resolve()
|
|
391
|
+
|
|
384
392
|
for concept in resolved.output_concepts:
|
|
385
393
|
validate_concept(
|
|
386
394
|
concept,
|
|
@@ -391,6 +399,7 @@ def validate_stack(
|
|
|
391
399
|
virtual_addresses,
|
|
392
400
|
found_map,
|
|
393
401
|
accept_partial,
|
|
402
|
+
seen,
|
|
394
403
|
)
|
|
395
404
|
for concept in node.virtual_output_concepts:
|
|
396
405
|
if concept.address in non_partial_addresses:
|
|
@@ -480,6 +489,7 @@ def _search_concepts(
|
|
|
480
489
|
|
|
481
490
|
found: set[str] = set()
|
|
482
491
|
skip: set[str] = set()
|
|
492
|
+
virtual: set[str] = set()
|
|
483
493
|
stack: List[StrategyNode] = []
|
|
484
494
|
complete = ValidationResult.INCOMPLETE
|
|
485
495
|
|