pytrilogy 0.0.2.3__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.3.dist-info → pytrilogy-0.0.2.4.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.2.3.dist-info → pytrilogy-0.0.2.4.dist-info}/RECORD +27 -27
- trilogy/__init__.py +1 -1
- trilogy/core/enums.py +1 -0
- trilogy/core/functions.py +9 -1
- trilogy/core/models.py +18 -1
- trilogy/core/optimizations/predicate_pushdown.py +3 -44
- trilogy/core/processing/concept_strategies_v3.py +1 -0
- trilogy/core/processing/node_generators/common.py +3 -3
- trilogy/core/processing/node_generators/filter_node.py +20 -16
- trilogy/core/processing/nodes/group_node.py +28 -2
- trilogy/core/processing/utility.py +46 -0
- trilogy/core/query_processor.py +7 -2
- trilogy/dialect/base.py +31 -6
- trilogy/dialect/bigquery.py +3 -1
- trilogy/dialect/duckdb.py +5 -4
- trilogy/dialect/postgres.py +3 -1
- trilogy/dialect/presto.py +3 -1
- trilogy/dialect/snowflake.py +3 -1
- trilogy/dialect/sql_server.py +3 -1
- trilogy/parsing/common.py +1 -0
- trilogy/parsing/parse_engine.py +74 -2
- trilogy/parsing/trilogy.lark +7 -3
- {pytrilogy-0.0.2.3.dist-info → pytrilogy-0.0.2.4.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.3.dist-info → pytrilogy-0.0.2.4.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.2.3.dist-info → pytrilogy-0.0.2.4.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.2.3.dist-info → pytrilogy-0.0.2.4.dist-info}/top_level.txt +0 -0
|
@@ -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,30 +8,30 @@ 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=
|
|
11
|
+
trilogy/core/enums.py,sha256=ozgw8G0s4CdbL6IUsX5nNT_ZGd4mMkoo8_EGLZXeQYY,5902
|
|
12
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=
|
|
19
|
+
trilogy/core/models.py,sha256=KVPUfupZVhQxJZCViMP7PELzvNR7WOwkjmqqgEM4oeM,137120
|
|
20
20
|
trilogy/core/optimization.py,sha256=A8S9C9H5RcQcFSQLYtEEBnm-r1CW_e9GEWlLK7q3MqA,4930
|
|
21
|
-
trilogy/core/query_processor.py,sha256=
|
|
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
32
|
trilogy/core/processing/node_generators/basic_node.py,sha256=4242PNGTCm2tklqMIkqVu5Iv4m_IeTnOYXxDveuCDZM,2856
|
|
33
|
-
trilogy/core/processing/node_generators/common.py,sha256=
|
|
34
|
-
trilogy/core/processing/node_generators/filter_node.py,sha256=
|
|
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
|
|
@@ -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/functions.py
CHANGED
|
@@ -251,11 +251,19 @@ def MapAccess(args: list[Concept]):
|
|
|
251
251
|
)
|
|
252
252
|
|
|
253
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
|
+
|
|
254
262
|
def AttrAccess(args: list[Concept]):
|
|
255
263
|
return Function(
|
|
256
264
|
operator=FunctionType.ATTR_ACCESS,
|
|
257
265
|
arguments=args,
|
|
258
|
-
output_datatype=args[0]
|
|
266
|
+
output_datatype=get_attr_datatype(args[0], args[1]), # type: ignore
|
|
259
267
|
output_purpose=Purpose.PROPERTY,
|
|
260
268
|
valid_inputs=[
|
|
261
269
|
{DataType.STRUCT},
|
trilogy/core/models.py
CHANGED
|
@@ -296,7 +296,7 @@ class MapType(BaseModel):
|
|
|
296
296
|
|
|
297
297
|
class StructType(BaseModel):
|
|
298
298
|
fields: List[ALL_TYPES]
|
|
299
|
-
fields_map: Dict[str, Concept] = Field(default_factory=dict)
|
|
299
|
+
fields_map: Dict[str, Concept | int | float | str] = Field(default_factory=dict)
|
|
300
300
|
|
|
301
301
|
@property
|
|
302
302
|
def data_type(self):
|
|
@@ -419,6 +419,23 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
419
419
|
def __hash__(self):
|
|
420
420
|
return hash(str(self))
|
|
421
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
|
+
|
|
422
439
|
def with_merge(self, source: Concept, target: Concept, modifiers: List[Modifier]):
|
|
423
440
|
if self.address == source.address:
|
|
424
441
|
new = target.with_grain(self.grain.with_merge(source, target, modifiers))
|
|
@@ -6,16 +6,9 @@ from trilogy.core.models import (
|
|
|
6
6
|
SubselectComparison,
|
|
7
7
|
Comparison,
|
|
8
8
|
Parenthetical,
|
|
9
|
-
Function,
|
|
10
|
-
FilterItem,
|
|
11
|
-
MagicConstants,
|
|
12
|
-
Concept,
|
|
13
|
-
WindowItem,
|
|
14
|
-
AggregateWrapper,
|
|
15
|
-
DataType,
|
|
16
9
|
)
|
|
17
10
|
from trilogy.core.optimizations.base_optimization import OptimizationRule
|
|
18
|
-
from trilogy.core.
|
|
11
|
+
from trilogy.core.processing.utility import is_scalar_condition
|
|
19
12
|
|
|
20
13
|
|
|
21
14
|
def decompose_condition(
|
|
@@ -56,40 +49,6 @@ def is_child_of(a, comparison):
|
|
|
56
49
|
return base
|
|
57
50
|
|
|
58
51
|
|
|
59
|
-
def is_basic(
|
|
60
|
-
element: (
|
|
61
|
-
int
|
|
62
|
-
| str
|
|
63
|
-
| float
|
|
64
|
-
| list
|
|
65
|
-
| WindowItem
|
|
66
|
-
| FilterItem
|
|
67
|
-
| Concept
|
|
68
|
-
| Comparison
|
|
69
|
-
| Conditional
|
|
70
|
-
| Parenthetical
|
|
71
|
-
| Function
|
|
72
|
-
| AggregateWrapper
|
|
73
|
-
| MagicConstants
|
|
74
|
-
| DataType
|
|
75
|
-
),
|
|
76
|
-
) -> bool:
|
|
77
|
-
if isinstance(element, Parenthetical):
|
|
78
|
-
return is_basic(element.content)
|
|
79
|
-
elif isinstance(element, SubselectComparison):
|
|
80
|
-
return True
|
|
81
|
-
elif isinstance(element, Comparison):
|
|
82
|
-
return is_basic(element.left) and is_basic(element.right)
|
|
83
|
-
elif isinstance(element, Function):
|
|
84
|
-
if element.operator in FunctionClass.AGGREGATE_FUNCTIONS.value:
|
|
85
|
-
return False
|
|
86
|
-
elif isinstance(element, AggregateWrapper):
|
|
87
|
-
return is_basic(element.function)
|
|
88
|
-
elif isinstance(element, Conditional):
|
|
89
|
-
return is_basic(element.left) and is_basic(element.right)
|
|
90
|
-
return True
|
|
91
|
-
|
|
92
|
-
|
|
93
52
|
class PredicatePushdown(OptimizationRule):
|
|
94
53
|
|
|
95
54
|
def __init__(self, *args, **kwargs) -> None:
|
|
@@ -187,13 +146,13 @@ class PredicatePushdown(OptimizationRule):
|
|
|
187
146
|
)
|
|
188
147
|
optimized = False
|
|
189
148
|
for candidate in candidates:
|
|
190
|
-
if not
|
|
149
|
+
if not is_scalar_condition(candidate):
|
|
191
150
|
self.debug(
|
|
192
151
|
f"Skipping {candidate} as not a basic [no aggregate, etc] condition"
|
|
193
152
|
)
|
|
194
153
|
continue
|
|
195
154
|
self.log(
|
|
196
|
-
f"Checking candidate {candidate}, {type(candidate)}, {
|
|
155
|
+
f"Checking candidate {candidate}, {type(candidate)}, scalar: {is_scalar_condition(candidate)}"
|
|
197
156
|
)
|
|
198
157
|
for parent_cte in cte.parent_ctes:
|
|
199
158
|
local_pushdown = self._check_parent(
|
|
@@ -47,7 +47,7 @@ def resolve_function_parent_concepts(concept: Concept) -> List[Concept]:
|
|
|
47
47
|
|
|
48
48
|
def resolve_filter_parent_concepts(
|
|
49
49
|
concept: Concept,
|
|
50
|
-
) -> Tuple[Concept, List[Concept], List[Concept]]:
|
|
50
|
+
) -> Tuple[Concept, List[Concept], List[Tuple[Concept, ...]]]:
|
|
51
51
|
if not isinstance(concept.lineage, FilterItem):
|
|
52
52
|
raise ValueError(
|
|
53
53
|
f"Concept {concept} lineage is not filter item, is {type(concept.lineage)}"
|
|
@@ -58,7 +58,7 @@ def resolve_filter_parent_concepts(
|
|
|
58
58
|
base_rows += concept.lineage.where.row_arguments
|
|
59
59
|
# TODO: pass tuple groups through
|
|
60
60
|
for ctuple in concept.lineage.where.existence_arguments:
|
|
61
|
-
base_existence
|
|
61
|
+
base_existence.append(ctuple)
|
|
62
62
|
if direct_parent.grain:
|
|
63
63
|
base_rows += direct_parent.grain.components_copy
|
|
64
64
|
if (
|
|
@@ -71,7 +71,7 @@ def resolve_filter_parent_concepts(
|
|
|
71
71
|
return (
|
|
72
72
|
concept.lineage.content,
|
|
73
73
|
unique(base_rows, "address"),
|
|
74
|
-
|
|
74
|
+
base_existence,
|
|
75
75
|
)
|
|
76
76
|
return concept.lineage.content, unique(base_rows, "address"), []
|
|
77
77
|
|
|
@@ -74,27 +74,31 @@ def gen_filter_node(
|
|
|
74
74
|
return parent
|
|
75
75
|
|
|
76
76
|
core_parents.append(parent)
|
|
77
|
+
|
|
77
78
|
if parent_existence_concepts:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
parent_existence = source_concepts(
|
|
82
|
-
mandatory_list=parent_existence_concepts,
|
|
83
|
-
environment=environment,
|
|
84
|
-
g=g,
|
|
85
|
-
depth=depth + 1,
|
|
86
|
-
history=history,
|
|
87
|
-
)
|
|
88
|
-
if not parent_existence:
|
|
79
|
+
for existence_tuple in parent_existence_concepts:
|
|
80
|
+
if not existence_tuple:
|
|
81
|
+
continue
|
|
89
82
|
logger.info(
|
|
90
|
-
f"{padding(depth)}{LOGGER_PREFIX} filter existence
|
|
83
|
+
f"{padding(depth)}{LOGGER_PREFIX} fetching filter node existence parents {[x.address for x in existence_tuple]}"
|
|
91
84
|
)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
85
|
+
parent_existence = source_concepts(
|
|
86
|
+
mandatory_list=list(existence_tuple),
|
|
87
|
+
environment=environment,
|
|
88
|
+
g=g,
|
|
89
|
+
depth=depth + 1,
|
|
90
|
+
history=history,
|
|
91
|
+
)
|
|
92
|
+
if not parent_existence:
|
|
93
|
+
logger.info(
|
|
94
|
+
f"{padding(depth)}{LOGGER_PREFIX} filter existence node parents could not be found"
|
|
95
|
+
)
|
|
96
|
+
return None
|
|
97
|
+
core_parents.append(parent_existence)
|
|
98
|
+
flattened_existence = [x for y in parent_existence_concepts for x in y]
|
|
95
99
|
filter_node = FilterNode(
|
|
96
100
|
input_concepts=unique(
|
|
97
|
-
[immediate_parent] + parent_row_concepts +
|
|
101
|
+
[immediate_parent] + parent_row_concepts + flattened_existence,
|
|
98
102
|
"address",
|
|
99
103
|
),
|
|
100
104
|
output_concepts=[concept, immediate_parent] + parent_row_concepts,
|
|
@@ -19,7 +19,7 @@ from trilogy.core.processing.nodes.base_node import (
|
|
|
19
19
|
concept_list_to_grain,
|
|
20
20
|
)
|
|
21
21
|
from trilogy.utility import unique
|
|
22
|
-
|
|
22
|
+
from trilogy.core.processing.utility import is_scalar_condition
|
|
23
23
|
|
|
24
24
|
LOGGER_PREFIX = "[CONCEPT DETAIL - GROUP NODE]"
|
|
25
25
|
|
|
@@ -111,7 +111,8 @@ class GroupNode(StrategyNode):
|
|
|
111
111
|
f" {parent.grain}"
|
|
112
112
|
)
|
|
113
113
|
source_type = SourceType.GROUP
|
|
114
|
-
|
|
114
|
+
|
|
115
|
+
base = QueryDatasource(
|
|
115
116
|
input_concepts=self.input_concepts,
|
|
116
117
|
output_concepts=self.output_concepts,
|
|
117
118
|
datasources=parent_sources,
|
|
@@ -134,6 +135,31 @@ class GroupNode(StrategyNode):
|
|
|
134
135
|
partial_concepts=self.partial_concepts,
|
|
135
136
|
condition=self.conditions,
|
|
136
137
|
)
|
|
138
|
+
# if there is a condition on a group node and it's not scalar
|
|
139
|
+
# inject an additional CTE
|
|
140
|
+
if self.conditions:
|
|
141
|
+
logger.info("CONDITIONS")
|
|
142
|
+
logger.info(str(self.conditions))
|
|
143
|
+
logger.info(is_scalar_condition(self.conditions))
|
|
144
|
+
if self.conditions and not is_scalar_condition(self.conditions):
|
|
145
|
+
base.condition = None
|
|
146
|
+
base.output_concepts = self.output_concepts + self.conditions.row_arguments
|
|
147
|
+
return QueryDatasource(
|
|
148
|
+
input_concepts=base.output_concepts,
|
|
149
|
+
output_concepts=self.output_concepts,
|
|
150
|
+
datasources=[base],
|
|
151
|
+
source_type=SourceType.SELECT,
|
|
152
|
+
source_map=resolve_concept_map(
|
|
153
|
+
[base],
|
|
154
|
+
targets=self.output_concepts,
|
|
155
|
+
inherited_inputs=base.output_concepts,
|
|
156
|
+
),
|
|
157
|
+
joins=[],
|
|
158
|
+
grain=grain,
|
|
159
|
+
partial_concepts=self.partial_concepts,
|
|
160
|
+
condition=self.conditions,
|
|
161
|
+
)
|
|
162
|
+
return base
|
|
137
163
|
|
|
138
164
|
def copy(self) -> "GroupNode":
|
|
139
165
|
return GroupNode(
|
|
@@ -8,6 +8,16 @@ from trilogy.core.models import (
|
|
|
8
8
|
QueryDatasource,
|
|
9
9
|
LooseConceptList,
|
|
10
10
|
Environment,
|
|
11
|
+
Conditional,
|
|
12
|
+
SubselectComparison,
|
|
13
|
+
Comparison,
|
|
14
|
+
Parenthetical,
|
|
15
|
+
Function,
|
|
16
|
+
FilterItem,
|
|
17
|
+
MagicConstants,
|
|
18
|
+
WindowItem,
|
|
19
|
+
AggregateWrapper,
|
|
20
|
+
DataType,
|
|
11
21
|
)
|
|
12
22
|
|
|
13
23
|
from trilogy.core.enums import Purpose, Granularity
|
|
@@ -18,6 +28,8 @@ from collections import defaultdict
|
|
|
18
28
|
from logging import Logger
|
|
19
29
|
from pydantic import BaseModel
|
|
20
30
|
|
|
31
|
+
from trilogy.core.enums import FunctionClass
|
|
32
|
+
|
|
21
33
|
|
|
22
34
|
class NodeType(Enum):
|
|
23
35
|
CONCEPT = 1
|
|
@@ -332,3 +344,37 @@ def get_disconnected_components(
|
|
|
332
344
|
x for x in sub_graphs if calculate_graph_relevance(graph, x, all_concepts) > 0
|
|
333
345
|
]
|
|
334
346
|
return len(sub_graphs), sub_graphs
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def is_scalar_condition(
|
|
350
|
+
element: (
|
|
351
|
+
int
|
|
352
|
+
| str
|
|
353
|
+
| float
|
|
354
|
+
| list
|
|
355
|
+
| WindowItem
|
|
356
|
+
| FilterItem
|
|
357
|
+
| Concept
|
|
358
|
+
| Comparison
|
|
359
|
+
| Conditional
|
|
360
|
+
| Parenthetical
|
|
361
|
+
| Function
|
|
362
|
+
| AggregateWrapper
|
|
363
|
+
| MagicConstants
|
|
364
|
+
| DataType
|
|
365
|
+
),
|
|
366
|
+
) -> bool:
|
|
367
|
+
if isinstance(element, Parenthetical):
|
|
368
|
+
return is_scalar_condition(element.content)
|
|
369
|
+
elif isinstance(element, SubselectComparison):
|
|
370
|
+
return True
|
|
371
|
+
elif isinstance(element, Comparison):
|
|
372
|
+
return is_scalar_condition(element.left) and is_scalar_condition(element.right)
|
|
373
|
+
elif isinstance(element, Function):
|
|
374
|
+
if element.operator in FunctionClass.AGGREGATE_FUNCTIONS.value:
|
|
375
|
+
return False
|
|
376
|
+
elif isinstance(element, AggregateWrapper):
|
|
377
|
+
return is_scalar_condition(element.function)
|
|
378
|
+
elif isinstance(element, Conditional):
|
|
379
|
+
return is_scalar_condition(element.left) and is_scalar_condition(element.right)
|
|
380
|
+
return True
|
trilogy/core/query_processor.py
CHANGED
|
@@ -350,7 +350,11 @@ def get_query_datasources(
|
|
|
350
350
|
|
|
351
351
|
search_concepts: list[Concept] = statement.output_components
|
|
352
352
|
nest_where = statement.where_clause_category == SelectFiltering.IMPLICIT
|
|
353
|
-
|
|
353
|
+
|
|
354
|
+
# if all are aggregates, we've pushed the filtering inside the aggregates anyway
|
|
355
|
+
all_aggregate = all([x.is_aggregate for x in search_concepts])
|
|
356
|
+
|
|
357
|
+
if nest_where and statement.where_clause and not all_aggregate:
|
|
354
358
|
search_concepts = unique(
|
|
355
359
|
statement.where_clause.row_arguments + search_concepts, "address"
|
|
356
360
|
)
|
|
@@ -363,7 +367,8 @@ def get_query_datasources(
|
|
|
363
367
|
)
|
|
364
368
|
ds: GroupNode | SelectNode
|
|
365
369
|
if nest_where and statement.where_clause:
|
|
366
|
-
|
|
370
|
+
if not all_aggregate:
|
|
371
|
+
ods.conditions = statement.where_clause.conditional
|
|
367
372
|
ods.output_concepts = search_concepts
|
|
368
373
|
# ods.hidden_concepts = where_delta
|
|
369
374
|
ods.rebuild_cache()
|
trilogy/dialect/base.py
CHANGED
|
@@ -2,6 +2,7 @@ from typing import List, Union, Optional, Dict, Any, Sequence, Callable
|
|
|
2
2
|
|
|
3
3
|
from jinja2 import Template
|
|
4
4
|
|
|
5
|
+
from trilogy.core.processing.utility import is_scalar_condition
|
|
5
6
|
from trilogy.constants import CONFIG, logger, MagicConstants
|
|
6
7
|
from trilogy.core.internal import DEFAULT_CONCEPTS
|
|
7
8
|
from trilogy.core.enums import (
|
|
@@ -46,6 +47,7 @@ from trilogy.core.models import (
|
|
|
46
47
|
ProcessedRawSQLStatement,
|
|
47
48
|
NumericType,
|
|
48
49
|
MapType,
|
|
50
|
+
StructType,
|
|
49
51
|
MergeStatementV2,
|
|
50
52
|
)
|
|
51
53
|
from trilogy.core.query_processor import process_query, process_persist
|
|
@@ -106,6 +108,10 @@ def render_case(args):
|
|
|
106
108
|
return "CASE\n\t" + "\n\t".join(args) + "\n\tEND"
|
|
107
109
|
|
|
108
110
|
|
|
111
|
+
def struct_arg(args):
|
|
112
|
+
return [f"{x[0]}: {x[1]}" for x in zip(args[::2], args[1::2])]
|
|
113
|
+
|
|
114
|
+
|
|
109
115
|
FUNCTION_MAP = {
|
|
110
116
|
# generic types
|
|
111
117
|
FunctionType.ALIAS: lambda x: f"{x[0]}",
|
|
@@ -120,6 +126,8 @@ FUNCTION_MAP = {
|
|
|
120
126
|
FunctionType.INDEX_ACCESS: lambda x: f"{x[0]}[{x[1]}]",
|
|
121
127
|
FunctionType.MAP_ACCESS: lambda x: f"{x[0]}[{x[1]}][1]",
|
|
122
128
|
FunctionType.UNNEST: lambda x: f"unnest({x[0]})",
|
|
129
|
+
FunctionType.ATTR_ACCESS: lambda x: f"""{x[0]}.{x[1].replace("'", "")}""",
|
|
130
|
+
FunctionType.STRUCT: lambda x: f"{{{', '.join(struct_arg(x))}}}",
|
|
123
131
|
# math
|
|
124
132
|
FunctionType.ADD: lambda x: f"{x[0]} + {x[1]}",
|
|
125
133
|
FunctionType.SUBTRACT: lambda x: f"{x[0]} - {x[1]}",
|
|
@@ -164,7 +172,6 @@ FUNCTION_MAP = {
|
|
|
164
172
|
# constant types
|
|
165
173
|
FunctionType.CURRENT_DATE: lambda x: "current_date()",
|
|
166
174
|
FunctionType.CURRENT_DATETIME: lambda x: "current_datetime()",
|
|
167
|
-
FunctionType.ATTR_ACCESS: lambda x: f"""{x[0]}.{x[1].replace("'", "")}""",
|
|
168
175
|
}
|
|
169
176
|
|
|
170
177
|
FUNCTION_GRAIN_MATCH_MAP = {
|
|
@@ -196,7 +203,9 @@ TOP {{ limit }}{% endif %}
|
|
|
196
203
|
WHERE
|
|
197
204
|
\t{{ where }}{% endif %}{%- if group_by %}
|
|
198
205
|
GROUP BY {% for group in group_by %}
|
|
199
|
-
\t{{group}}{% if not loop.last %},{% endif %}{% endfor %}{% endif %}{
|
|
206
|
+
\t{{group}}{% if not loop.last %},{% endif %}{% endfor %}{% endif %}{% if having %}
|
|
207
|
+
HAVING
|
|
208
|
+
\t{{ having }}{% endif %}{%- if order_by %}
|
|
200
209
|
ORDER BY{% for order in order_by %}
|
|
201
210
|
\t{{ order }}{% if not loop.last %},{% endif %}{% endfor %}
|
|
202
211
|
{% endif %}{% endif %}
|
|
@@ -347,6 +356,7 @@ class BaseDialect:
|
|
|
347
356
|
MapWrapper[Any, Any],
|
|
348
357
|
MapType,
|
|
349
358
|
NumericType,
|
|
359
|
+
StructType,
|
|
350
360
|
ListType,
|
|
351
361
|
ListWrapper[Any],
|
|
352
362
|
DatePart,
|
|
@@ -370,11 +380,18 @@ class BaseDialect:
|
|
|
370
380
|
lookup_cte = cte_map.get(e.right.address)
|
|
371
381
|
assert lookup_cte, "Subselects must be rendered with a CTE in context"
|
|
372
382
|
if e.right.address not in lookup_cte.existence_source_map:
|
|
373
|
-
lookup = lookup_cte.source_map
|
|
383
|
+
lookup = lookup_cte.source_map.get(
|
|
384
|
+
e.right.address,
|
|
385
|
+
[
|
|
386
|
+
INVALID_REFERENCE_STRING(
|
|
387
|
+
f"Missing source reference to {e.right.name}"
|
|
388
|
+
)
|
|
389
|
+
],
|
|
390
|
+
)
|
|
374
391
|
else:
|
|
375
392
|
lookup = lookup_cte.existence_source_map[e.right.address]
|
|
376
393
|
|
|
377
|
-
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map)} {e.operator.value} (select {lookup[0]}.{self.QUOTE_CHARACTER}{e.right.safe_address}{self.QUOTE_CHARACTER} from {lookup[0]})"
|
|
394
|
+
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map)} {e.operator.value} (select {lookup[0]}.{self.QUOTE_CHARACTER}{e.right.safe_address}{self.QUOTE_CHARACTER} from {lookup[0]} where {lookup[0]}.{self.QUOTE_CHARACTER}{e.right.safe_address}{self.QUOTE_CHARACTER} is not null)"
|
|
378
395
|
elif isinstance(e.right, (ListWrapper, Parenthetical, list)):
|
|
379
396
|
return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map)} {e.operator.value} {self.render_expr(e.right, cte=cte, cte_map=cte_map)}"
|
|
380
397
|
|
|
@@ -418,6 +435,7 @@ class BaseDialect:
|
|
|
418
435
|
elif isinstance(e, CaseElse):
|
|
419
436
|
return f"ELSE {self.render_expr(e.expr, cte=cte, cte_map=cte_map) }"
|
|
420
437
|
elif isinstance(e, Function):
|
|
438
|
+
|
|
421
439
|
if cte and cte.group_to_grain:
|
|
422
440
|
return self.FUNCTION_MAP[e.operator](
|
|
423
441
|
[self.render_expr(z, cte=cte, cte_map=cte_map) for z in e.arguments]
|
|
@@ -507,8 +525,15 @@ class BaseDialect:
|
|
|
507
525
|
if j
|
|
508
526
|
],
|
|
509
527
|
where=(
|
|
510
|
-
self.render_expr(cte.condition, cte)
|
|
511
|
-
|
|
528
|
+
self.render_expr(cte.condition, cte)
|
|
529
|
+
if cte.condition and is_scalar_condition(cte.condition)
|
|
530
|
+
else None
|
|
531
|
+
),
|
|
532
|
+
having=(
|
|
533
|
+
self.render_expr(cte.condition, cte)
|
|
534
|
+
if cte.condition and not is_scalar_condition(cte.condition)
|
|
535
|
+
else None
|
|
536
|
+
),
|
|
512
537
|
order_by=(
|
|
513
538
|
[self.render_order_item(i, cte) for i in cte.order_by.items]
|
|
514
539
|
if cte.order_by
|
trilogy/dialect/bigquery.py
CHANGED
|
@@ -59,7 +59,9 @@ SELECT
|
|
|
59
59
|
{{ where }}
|
|
60
60
|
{% endif %}
|
|
61
61
|
{%- if group_by %}GROUP BY {% for group in group_by %}
|
|
62
|
-
{{group}}{% if not loop.last %},{% endif %}{% endfor %}{% endif %}
|
|
62
|
+
{{group}}{% if not loop.last %},{% endif %}{% endfor %}{% endif %}{% if having %}
|
|
63
|
+
HAVING
|
|
64
|
+
\t{{ having }}{% endif %}
|
|
63
65
|
{%- if order_by %}
|
|
64
66
|
ORDER BY {% for order in order_by %}
|
|
65
67
|
{{ order }}{% if not loop.last %},{% endif %}{% endfor %}{% endif %}
|
trilogy/dialect/duckdb.py
CHANGED
|
@@ -59,11 +59,12 @@ SELECT
|
|
|
59
59
|
{{ join }}{% endfor %}{% endif %}
|
|
60
60
|
{% if where %}WHERE
|
|
61
61
|
{{ where }}
|
|
62
|
-
{% endif -%}
|
|
63
|
-
{%- if group_by %}
|
|
62
|
+
{% endif -%}{%- if group_by %}
|
|
64
63
|
GROUP BY {% for group in group_by %}
|
|
65
|
-
{{group}}{% if not loop.last %},{% endif %}{% endfor %}{% endif %}
|
|
66
|
-
|
|
64
|
+
{{group}}{% if not loop.last %},{% endif %}{% endfor %}{% endif %}{% if having %}
|
|
65
|
+
HAVING
|
|
66
|
+
{{ having }}
|
|
67
|
+
{% endif %}{%- if order_by %}
|
|
67
68
|
ORDER BY {% for order in order_by %}
|
|
68
69
|
{{ order }}{% if not loop.last %},{% endif %}{% endfor %}{% endif %}
|
|
69
70
|
{%- if limit is not none %}
|
trilogy/dialect/postgres.py
CHANGED
|
@@ -64,7 +64,9 @@ SELECT
|
|
|
64
64
|
{{ where }}
|
|
65
65
|
{% endif %}
|
|
66
66
|
{%- if group_by %}GROUP BY {% for group in group_by %}
|
|
67
|
-
{{group}}{% if not loop.last %},{% endif %}{% endfor %}{% endif %}
|
|
67
|
+
{{group}}{% if not loop.last %},{% endif %}{% endfor %}{% endif %}{% if having %}
|
|
68
|
+
HAVING
|
|
69
|
+
\t{{ having }}{% endif %}
|
|
68
70
|
{%- if order_by %}
|
|
69
71
|
ORDER BY {% for order in order_by %}
|
|
70
72
|
{{ order }}{% if not loop.last %},{% endif %}
|
trilogy/dialect/presto.py
CHANGED
|
@@ -63,7 +63,9 @@ SELECT
|
|
|
63
63
|
{{ where }}
|
|
64
64
|
{% endif %}
|
|
65
65
|
{%- if group_by %}GROUP BY {% for group in group_by %}
|
|
66
|
-
{{group}}{% if not loop.last %},{% endif %}{% endfor %}{% endif %}
|
|
66
|
+
{{group}}{% if not loop.last %},{% endif %}{% endfor %}{% endif %}{% if having %}
|
|
67
|
+
HAVING
|
|
68
|
+
\t{{ having }}{% endif %}
|
|
67
69
|
{%- if order_by %}
|
|
68
70
|
ORDER BY {% for order in order_by %}
|
|
69
71
|
{{ order }}{% if not loop.last %},{% endif %}{% endfor %}{% endif %}
|
trilogy/dialect/snowflake.py
CHANGED
|
@@ -61,7 +61,9 @@ SELECT
|
|
|
61
61
|
{{ where }}
|
|
62
62
|
{% endif %}
|
|
63
63
|
{%- if group_by %}GROUP BY {% for group in group_by %}
|
|
64
|
-
{{group}}{% if not loop.last %},{% endif %}{% endfor %}{% endif %}
|
|
64
|
+
{{group}}{% if not loop.last %},{% endif %}{% endfor %}{% endif %}{% if having %}
|
|
65
|
+
HAVING
|
|
66
|
+
\t{{ having }}{% endif %}
|
|
65
67
|
{%- if order_by %}
|
|
66
68
|
ORDER BY {% for order in order_by %}
|
|
67
69
|
{{ order }}{% if not loop.last %},{% endif %}{% endfor %}{% endif %}
|
trilogy/dialect/sql_server.py
CHANGED
|
@@ -60,7 +60,9 @@ TOP {{ limit }}{% endif %}
|
|
|
60
60
|
{%- if group_by %}
|
|
61
61
|
GROUP BY {% for group in group_by %}
|
|
62
62
|
{{group}}{% if not loop.last %},{% endif %}
|
|
63
|
-
{% endfor %}{% endif %}
|
|
63
|
+
{% endfor %}{% endif %}{% if having %}
|
|
64
|
+
HAVING
|
|
65
|
+
\t{{ having }}{% endif %}
|
|
64
66
|
{%- if order_by %}
|
|
65
67
|
ORDER BY {% for order in order_by %}
|
|
66
68
|
{{ order }}{% if not loop.last %},{% endif %}
|
trilogy/parsing/common.py
CHANGED
|
@@ -198,6 +198,7 @@ def arbitrary_to_concept(
|
|
|
198
198
|
metadata: Metadata | None = None,
|
|
199
199
|
purpose: Purpose | None = None,
|
|
200
200
|
) -> Concept:
|
|
201
|
+
|
|
201
202
|
if isinstance(parent, AggregateWrapper):
|
|
202
203
|
return agg_wrapper_to_concept(parent, namespace, name, metadata, purpose)
|
|
203
204
|
elif isinstance(parent, WindowItem):
|
trilogy/parsing/parse_engine.py
CHANGED
|
@@ -500,7 +500,53 @@ class ParseToObjects(Transformer):
|
|
|
500
500
|
# we need to strip off every parenthetical to see what is being assigned.
|
|
501
501
|
while isinstance(source_value, Parenthetical):
|
|
502
502
|
source_value = source_value.content
|
|
503
|
-
|
|
503
|
+
|
|
504
|
+
if (
|
|
505
|
+
isinstance(source_value, Function)
|
|
506
|
+
and source_value.operator == FunctionType.STRUCT
|
|
507
|
+
):
|
|
508
|
+
concept = arbitrary_to_concept(
|
|
509
|
+
source_value,
|
|
510
|
+
name=name,
|
|
511
|
+
namespace=namespace,
|
|
512
|
+
purpose=purpose,
|
|
513
|
+
metadata=metadata,
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
if concept.metadata:
|
|
517
|
+
concept.metadata.line_number = meta.line
|
|
518
|
+
self.environment.add_concept(concept, meta=meta)
|
|
519
|
+
assert isinstance(concept.datatype, StructType)
|
|
520
|
+
for key, value in concept.datatype.fields_map.items():
|
|
521
|
+
args = self.process_function_args([concept, key], meta=meta)
|
|
522
|
+
self.environment.add_concept(
|
|
523
|
+
Concept(
|
|
524
|
+
name=key,
|
|
525
|
+
datatype=arg_to_datatype(value),
|
|
526
|
+
purpose=Purpose.PROPERTY,
|
|
527
|
+
namespace=self.environment.namespace + "." + name,
|
|
528
|
+
lineage=AttrAccess(args),
|
|
529
|
+
)
|
|
530
|
+
)
|
|
531
|
+
return ConceptDerivation(concept=concept)
|
|
532
|
+
elif (
|
|
533
|
+
isinstance(source_value, Function)
|
|
534
|
+
and source_value.operator == FunctionType.ALIAS
|
|
535
|
+
):
|
|
536
|
+
concept = arbitrary_to_concept(
|
|
537
|
+
source_value,
|
|
538
|
+
name=name,
|
|
539
|
+
namespace=namespace,
|
|
540
|
+
purpose=purpose,
|
|
541
|
+
metadata=metadata,
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
if concept.metadata:
|
|
545
|
+
concept.metadata.line_number = meta.line
|
|
546
|
+
self.environment.add_concept(concept, meta=meta)
|
|
547
|
+
return ConceptDerivation(concept=concept)
|
|
548
|
+
|
|
549
|
+
elif isinstance(
|
|
504
550
|
source_value, (FilterItem, WindowItem, AggregateWrapper, Function)
|
|
505
551
|
):
|
|
506
552
|
concept = arbitrary_to_concept(
|
|
@@ -515,6 +561,7 @@ class ParseToObjects(Transformer):
|
|
|
515
561
|
concept.metadata.line_number = meta.line
|
|
516
562
|
self.environment.add_concept(concept, meta=meta)
|
|
517
563
|
return ConceptDerivation(concept=concept)
|
|
564
|
+
|
|
518
565
|
elif isinstance(source_value, CONSTANT_TYPES):
|
|
519
566
|
concept = constant_to_concept(
|
|
520
567
|
source_value,
|
|
@@ -950,7 +997,15 @@ class ParseToObjects(Transformer):
|
|
|
950
997
|
isinstance(orderitem.expr, Concept)
|
|
951
998
|
and orderitem.expr.purpose == Purpose.METRIC
|
|
952
999
|
):
|
|
953
|
-
orderitem.expr = orderitem.expr.
|
|
1000
|
+
orderitem.expr = orderitem.expr.with_select_context(
|
|
1001
|
+
output.grain,
|
|
1002
|
+
conditional=(
|
|
1003
|
+
output.where_clause.conditional
|
|
1004
|
+
if output.where_clause
|
|
1005
|
+
and output.where_clause_category == SelectFiltering.IMPLICIT
|
|
1006
|
+
else None
|
|
1007
|
+
),
|
|
1008
|
+
)
|
|
954
1009
|
return output
|
|
955
1010
|
|
|
956
1011
|
@v_args(meta=True)
|
|
@@ -1016,6 +1071,18 @@ class ParseToObjects(Transformer):
|
|
|
1016
1071
|
def array_lit(self, args):
|
|
1017
1072
|
return list_to_wrapper(args)
|
|
1018
1073
|
|
|
1074
|
+
def struct_lit(self, args):
|
|
1075
|
+
|
|
1076
|
+
zipped = dict(zip(args[::2], args[1::2]))
|
|
1077
|
+
types = [arg_to_datatype(x) for x in args[1::2]]
|
|
1078
|
+
return Function(
|
|
1079
|
+
operator=FunctionType.STRUCT,
|
|
1080
|
+
output_datatype=StructType(fields=types, fields_map=zipped),
|
|
1081
|
+
output_purpose=function_args_to_output_purpose(args),
|
|
1082
|
+
arguments=args,
|
|
1083
|
+
arg_count=-1,
|
|
1084
|
+
)
|
|
1085
|
+
|
|
1019
1086
|
def map_lit(self, args):
|
|
1020
1087
|
parsed = dict(zip(args[::2], args[1::2]))
|
|
1021
1088
|
wrapped = dict_to_map_wrapper(parsed)
|
|
@@ -1162,6 +1229,11 @@ class ParseToObjects(Transformer):
|
|
|
1162
1229
|
return MapAccess(args)
|
|
1163
1230
|
return IndexAccess(args)
|
|
1164
1231
|
|
|
1232
|
+
@v_args(meta=True)
|
|
1233
|
+
def map_key_access(self, meta, args):
|
|
1234
|
+
args = self.process_function_args(args, meta=meta)
|
|
1235
|
+
return MapAccess(args)
|
|
1236
|
+
|
|
1165
1237
|
@v_args(meta=True)
|
|
1166
1238
|
def attr_access(self, meta, args):
|
|
1167
1239
|
args = self.process_function_args(args, meta=meta)
|
trilogy/parsing/trilogy.lark
CHANGED
|
@@ -153,9 +153,10 @@
|
|
|
153
153
|
unnest: _UNNEST expr ")"
|
|
154
154
|
//indexing into an expression is a function
|
|
155
155
|
index_access: expr "[" int_lit "]"
|
|
156
|
-
|
|
156
|
+
map_key_access: expr "[" _string_lit "]"
|
|
157
|
+
attr_access: expr "." _string_lit
|
|
157
158
|
|
|
158
|
-
expr: _constant_functions | window_item | filter_item | subselect_comparison | between_comparison | fgroup | aggregate_functions | unnest | _static_functions | literal | concept_lit | index_access | attr_access | parenthetical | expr_tuple | comparison | alt_like
|
|
159
|
+
expr: _constant_functions | window_item | filter_item | subselect_comparison | between_comparison | fgroup | aggregate_functions | unnest | _static_functions | literal | concept_lit | index_access | map_key_access | attr_access | parenthetical | expr_tuple | comparison | alt_like
|
|
159
160
|
|
|
160
161
|
// functions
|
|
161
162
|
|
|
@@ -276,12 +277,15 @@
|
|
|
276
277
|
array_lit: "[" (literal ",")* literal ","? "]"()
|
|
277
278
|
|
|
278
279
|
map_lit: "{" (literal ":" literal ",")* literal ":" literal ","? "}"
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
struct_lit: "struct" "(" (IDENTIFIER "=" literal ",")* IDENTIFIER "=" literal ","? ")"
|
|
279
283
|
|
|
280
284
|
!bool_lit: "True"i | "False"i
|
|
281
285
|
|
|
282
286
|
!null_lit.1: "null"i
|
|
283
287
|
|
|
284
|
-
literal: null_lit | _string_lit | int_lit | float_lit | bool_lit | array_lit | map_lit
|
|
288
|
+
literal: null_lit | _string_lit | int_lit | float_lit | bool_lit | array_lit | map_lit | struct_lit
|
|
285
289
|
|
|
286
290
|
MODIFIER: "Optional"i | "Partial"i | "Nullable"i
|
|
287
291
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|