pytrilogy 0.0.2.12__py3-none-any.whl → 0.0.2.14__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.12.dist-info → pytrilogy-0.0.2.14.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.2.12.dist-info → pytrilogy-0.0.2.14.dist-info}/RECORD +31 -31
- {pytrilogy-0.0.2.12.dist-info → pytrilogy-0.0.2.14.dist-info}/WHEEL +1 -1
- trilogy/__init__.py +1 -1
- trilogy/constants.py +16 -1
- trilogy/core/enums.py +3 -0
- trilogy/core/models.py +150 -17
- trilogy/core/optimizations/predicate_pushdown.py +1 -1
- trilogy/core/processing/node_generators/basic_node.py +8 -1
- trilogy/core/processing/node_generators/common.py +13 -36
- trilogy/core/processing/node_generators/filter_node.py +1 -15
- trilogy/core/processing/node_generators/group_node.py +19 -1
- trilogy/core/processing/node_generators/group_to_node.py +0 -12
- trilogy/core/processing/node_generators/multiselect_node.py +1 -10
- trilogy/core/processing/node_generators/rowset_node.py +3 -14
- trilogy/core/processing/node_generators/select_node.py +26 -0
- trilogy/core/processing/node_generators/window_node.py +1 -1
- trilogy/core/processing/nodes/base_node.py +40 -11
- trilogy/core/processing/nodes/group_node.py +31 -18
- trilogy/core/processing/nodes/merge_node.py +14 -5
- trilogy/core/processing/nodes/select_node_v2.py +4 -0
- trilogy/core/processing/utility.py +91 -3
- trilogy/core/query_processor.py +6 -12
- trilogy/dialect/common.py +10 -8
- trilogy/executor.py +8 -2
- trilogy/parsing/common.py +34 -4
- trilogy/parsing/parse_engine.py +31 -19
- trilogy/parsing/trilogy.lark +5 -5
- {pytrilogy-0.0.2.12.dist-info → pytrilogy-0.0.2.14.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.12.dist-info → pytrilogy-0.0.2.14.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.2.12.dist-info → pytrilogy-0.0.2.14.dist-info}/top_level.txt +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
trilogy/__init__.py,sha256=
|
|
1
|
+
trilogy/__init__.py,sha256=PE4Dy9UVhAPcaUZRWLaQUxC1U27eYhtTOKd-DOtVDh8,291
|
|
2
2
|
trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
trilogy/constants.py,sha256=
|
|
3
|
+
trilogy/constants.py,sha256=Ijos7_TEajKhZ7OJ_TreEYFddW1V33AVymDDrxP-ZHk,1234
|
|
4
4
|
trilogy/engine.py,sha256=R5ubIxYyrxRExz07aZCUfrTsoXCHQ8DKFTDsobXdWdA,1102
|
|
5
|
-
trilogy/executor.py,sha256=
|
|
5
|
+
trilogy/executor.py,sha256=PZr7IF8wS1Oi2WJGE-B3lp70Y8ue2uuauODw02chjdQ,11175
|
|
6
6
|
trilogy/parser.py,sha256=UtuqSiGiCjpMAYgo1bvNq-b7NSzCA5hzbUW31RXaMII,281
|
|
7
7
|
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=BRYqy-NgIacCYTJo0B11m5XQWSHq5pfxhoLd8pzA3ho,6025
|
|
12
12
|
trilogy/core/env_processor.py,sha256=l7TAB0LalxjTYJdTlcmFIkLXuyxa9lrenWLeZfa9qw0,2276
|
|
13
13
|
trilogy/core/environment_helpers.py,sha256=1miP4is4FEoci01KSAy2VZVYmlmT5TOCOALBekd2muQ,7211
|
|
14
14
|
trilogy/core/ergonomics.py,sha256=w3gwXdgrxNHCuaRdyKg73t6F36tj-wIjQf47WZkHmJk,1465
|
|
@@ -16,42 +16,42 @@ trilogy/core/exceptions.py,sha256=NvV_4qLOgKXbpotgRf7c8BANDEvHxlqRPaA53IThQ2o,56
|
|
|
16
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=bWCklm8A9I0vx8fXWoN0jKJjLkXuyMUUQetT_zbhyRc,149031
|
|
20
20
|
trilogy/core/optimization.py,sha256=7E-Ol51u6ZAxF56F_bzLxgRO-Hu6Yl1ZbPopZJB2tqk,7533
|
|
21
|
-
trilogy/core/query_processor.py,sha256=
|
|
21
|
+
trilogy/core/query_processor.py,sha256=qMVkaK1Lvr9jEftJwAidMdkb_tRx12G07qynEyl91C8,18801
|
|
22
22
|
trilogy/core/optimizations/__init__.py,sha256=bWQecbeiwiDx9LJnLsa7dkWxdbl2wcnkcTN69JyP8iI,356
|
|
23
23
|
trilogy/core/optimizations/base_optimization.py,sha256=tWWT-xnTbnEU-mNi_isMNbywm8B9WTRsNFwGpeh3rqE,468
|
|
24
24
|
trilogy/core/optimizations/inline_constant.py,sha256=kHNyc2UoaPVdYfVAPAFwnWuk4sJ_IF5faRtVcDOrBtw,1110
|
|
25
25
|
trilogy/core/optimizations/inline_datasource.py,sha256=AATzQ6YrtW_1-aQFjQyTYqEYKBoMFhek7ADfBr4uUdQ,3634
|
|
26
|
-
trilogy/core/optimizations/predicate_pushdown.py,sha256=
|
|
26
|
+
trilogy/core/optimizations/predicate_pushdown.py,sha256=1l9WnFOSv79e341typG3tTdk0XGl1J_ToQih3LYoGIY,8435
|
|
27
27
|
trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
28
|
trilogy/core/processing/concept_strategies_v3.py,sha256=ae6FmwiKNiEbOU2GhnzggFMh82MhqxBj9bgr0ituT2w,25633
|
|
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=jFLZmzxHq94q29FInr8XjS5YiqJOPPBYqh8Tlgs432Y,17722
|
|
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=
|
|
35
|
-
trilogy/core/processing/node_generators/group_node.py,sha256=
|
|
36
|
-
trilogy/core/processing/node_generators/group_to_node.py,sha256=
|
|
37
|
-
trilogy/core/processing/node_generators/multiselect_node.py,sha256=
|
|
32
|
+
trilogy/core/processing/node_generators/basic_node.py,sha256=IHj5jEloUe5yojGRLAzt35FcfHqGviWQdS8ETyvr39Q,3292
|
|
33
|
+
trilogy/core/processing/node_generators/common.py,sha256=3_Ivrq_wersDZ5pnvyHvsAUc07mRggxRGTiDq47O0Rk,8840
|
|
34
|
+
trilogy/core/processing/node_generators/filter_node.py,sha256=Tq9_Q6Xe95_-0S5H1EfCgwmKwVfXNoiclqV8QHKhvig,7723
|
|
35
|
+
trilogy/core/processing/node_generators/group_node.py,sha256=G7SrU2X5kjgzeqzzpnPscQBTDcFMc4m7TR6n8VHLC_A,3762
|
|
36
|
+
trilogy/core/processing/node_generators/group_to_node.py,sha256=yX0uw6YMxhyWVRMZoMFzEkJe3tB5ByFqrTnuRWVcRh4,2446
|
|
37
|
+
trilogy/core/processing/node_generators/multiselect_node.py,sha256=OUjndYjA8xR6yKr-J7R-JxDeYfO6DxmMNNcJiFJzk7g,6138
|
|
38
38
|
trilogy/core/processing/node_generators/node_merge_node.py,sha256=D_jsnfoLMrQc08_JvT0wEDvjyzJAxBpdcZFyDN-feV0,13192
|
|
39
|
-
trilogy/core/processing/node_generators/rowset_node.py,sha256=
|
|
40
|
-
trilogy/core/processing/node_generators/select_node.py,sha256=
|
|
39
|
+
trilogy/core/processing/node_generators/rowset_node.py,sha256=qYt0Ngk-WpmAA_gZZORXR-WpLdFqyYAuJiZnrxBmNvw,4201
|
|
40
|
+
trilogy/core/processing/node_generators/select_node.py,sha256=XSMA4kvFdoXlfCpbciXXkbexXkemwUorcAU6P3EwuZY,19843
|
|
41
41
|
trilogy/core/processing/node_generators/unnest_node.py,sha256=aZeixbOzMtXi7BPahKr9bOkIhTciyD9Klsj0kZ56F6s,2189
|
|
42
|
-
trilogy/core/processing/node_generators/window_node.py,sha256=
|
|
42
|
+
trilogy/core/processing/node_generators/window_node.py,sha256=LSlXe41elFGVRlxRX3MEFimhduGn3o5WE0kLx2JtA4M,3322
|
|
43
43
|
trilogy/core/processing/nodes/__init__.py,sha256=jyduHk96j5fpju72sc8swOiBjR3Md866kt8JZGkp3ZU,4866
|
|
44
|
-
trilogy/core/processing/nodes/base_node.py,sha256=
|
|
44
|
+
trilogy/core/processing/nodes/base_node.py,sha256=11Evv2LErwlzCU9ebLWlzldz7VbVMgYiR_sUkVYylKQ,13916
|
|
45
45
|
trilogy/core/processing/nodes/filter_node.py,sha256=DBOSGFfkiILrZa1BlLv2uxUSkgWtSIKiZplqyKXPjg8,2132
|
|
46
|
-
trilogy/core/processing/nodes/group_node.py,sha256=
|
|
47
|
-
trilogy/core/processing/nodes/merge_node.py,sha256=
|
|
48
|
-
trilogy/core/processing/nodes/select_node_v2.py,sha256=
|
|
46
|
+
trilogy/core/processing/nodes/group_node.py,sha256=3zyEs11hv9CoGpO62cUKfClcS58clTUB0IMIkmOV998,6897
|
|
47
|
+
trilogy/core/processing/nodes/merge_node.py,sha256=bn7CwZwbYFx-OjNLb9oQuYL_abwAAd_KSSYJGFSEiP8,15022
|
|
48
|
+
trilogy/core/processing/nodes/select_node_v2.py,sha256=yoU2PWu-BkiUDECd7V7CKAPjznB_LObpl52HU9Sk5Yc,7433
|
|
49
49
|
trilogy/core/processing/nodes/unnest_node.py,sha256=mAmFluzm2yeeiQ6NfIB7BU_8atRGh-UJfPf9ROwbhr8,2152
|
|
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
52
|
trilogy/dialect/base.py,sha256=kQek_ufZC9HDVOKlWasvIx6xyew8wv3JNIU6r53_IR4,32842
|
|
53
53
|
trilogy/dialect/bigquery.py,sha256=15KJ-cOpBlk9O7FPviPgmg8xIydJeKx7WfmL3SSsPE8,2953
|
|
54
|
-
trilogy/dialect/common.py,sha256=
|
|
54
|
+
trilogy/dialect/common.py,sha256=QCsqo5morOOL6kwaCYh1RBmaInaoPI6lKtzdgroWvuM,3440
|
|
55
55
|
trilogy/dialect/config.py,sha256=tLVEMctaTDhUgARKXUNfHUcIolGaALkQ0RavUvXAY4w,2994
|
|
56
56
|
trilogy/dialect/duckdb.py,sha256=u_gpL35kouWxoBLas1h0ABYY2QzlVtEh22hm5h0lCOM,3182
|
|
57
57
|
trilogy/dialect/enums.py,sha256=4NdpsydBpDn6jnh0JzFz5VvQEtnShErWtWHVyT6TNpw,3948
|
|
@@ -65,18 +65,18 @@ trilogy/hooks/graph_hook.py,sha256=onHvMQPwj_KOS3HOTpRFiy7QLLKAiycq2MzJ_Q0Oh5Y,2
|
|
|
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=-4LM71ocidA8DI2RngqFEOmhzBrIt8VdBTO4x2BpD8E,9502
|
|
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=_DNpdZOQoZZio_mW8nxcCKoJHny7hnQQtOGG1msoCFU,63265
|
|
73
73
|
trilogy/parsing/render.py,sha256=Gy_6wVYPwYLf35Iota08sbqveuWILtUhI8MYStcvtJM,12174
|
|
74
|
-
trilogy/parsing/trilogy.lark,sha256
|
|
74
|
+
trilogy/parsing/trilogy.lark,sha256=-9y4oVAIlKi-6pE58G4RwGGTBeG7P3T_V4gV8mILd8w,11549
|
|
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.14.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
78
|
+
pytrilogy-0.0.2.14.dist-info/METADATA,sha256=MqQy1FLZVBpbDh36uTVMwYujLjDB4JkYdz9r5cytOgA,7907
|
|
79
|
+
pytrilogy-0.0.2.14.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
80
|
+
pytrilogy-0.0.2.14.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
|
|
81
|
+
pytrilogy-0.0.2.14.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
82
|
+
pytrilogy-0.0.2.14.dist-info/RECORD,,
|
trilogy/__init__.py
CHANGED
trilogy/constants.py
CHANGED
|
@@ -28,15 +28,30 @@ class Optimizations:
|
|
|
28
28
|
direct_return: bool = True
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
@dataclass
|
|
32
|
+
class Comments:
|
|
33
|
+
"""Control what is placed in CTE comments"""
|
|
34
|
+
|
|
35
|
+
show: bool = False
|
|
36
|
+
basic: bool = True
|
|
37
|
+
joins: bool = True
|
|
38
|
+
nullable: bool = False
|
|
39
|
+
partial: bool = False
|
|
40
|
+
|
|
41
|
+
|
|
31
42
|
# TODO: support loading from environments
|
|
32
43
|
@dataclass
|
|
33
44
|
class Config:
|
|
34
45
|
strict_mode: bool = True
|
|
35
46
|
human_identifiers: bool = True
|
|
36
47
|
validate_missing: bool = True
|
|
37
|
-
|
|
48
|
+
comments: Comments = field(default_factory=Comments)
|
|
38
49
|
optimizations: Optimizations = field(default_factory=Optimizations)
|
|
39
50
|
|
|
51
|
+
@property
|
|
52
|
+
def show_comments(self) -> bool:
|
|
53
|
+
return self.comments.show
|
|
54
|
+
|
|
40
55
|
def set_random_seed(self, seed: int):
|
|
41
56
|
random.seed(seed)
|
|
42
57
|
|
trilogy/core/enums.py
CHANGED
|
@@ -62,6 +62,8 @@ class Modifier(Enum):
|
|
|
62
62
|
strval = str(value)
|
|
63
63
|
if strval == "~":
|
|
64
64
|
return Modifier.PARTIAL
|
|
65
|
+
elif strval == "?":
|
|
66
|
+
return Modifier.NULLABLE
|
|
65
67
|
return super()._missing_(value=strval.capitalize())
|
|
66
68
|
|
|
67
69
|
|
|
@@ -273,6 +275,7 @@ class SourceType(Enum):
|
|
|
273
275
|
CONSTANT = "constant"
|
|
274
276
|
ROWSET = "rowset"
|
|
275
277
|
MERGE = "merge"
|
|
278
|
+
BASIC = "basic"
|
|
276
279
|
|
|
277
280
|
|
|
278
281
|
class ShowCategory(Enum):
|
trilogy/core/models.py
CHANGED
|
@@ -79,6 +79,18 @@ VT = TypeVar("VT")
|
|
|
79
79
|
LT = TypeVar("LT")
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
def is_compatible_datatype(left, right):
|
|
83
|
+
if left == right:
|
|
84
|
+
return True
|
|
85
|
+
if {left, right} == {DataType.NUMERIC, DataType.FLOAT}:
|
|
86
|
+
return True
|
|
87
|
+
if {left, right} == {DataType.NUMERIC, DataType.INTEGER}:
|
|
88
|
+
return True
|
|
89
|
+
if {left, right} == {DataType.FLOAT, DataType.INTEGER}:
|
|
90
|
+
return True
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
|
|
82
94
|
def get_version():
|
|
83
95
|
from trilogy import __version__
|
|
84
96
|
|
|
@@ -220,6 +232,7 @@ class DataType(Enum):
|
|
|
220
232
|
ARRAY = "array"
|
|
221
233
|
DATE_PART = "date_part"
|
|
222
234
|
STRUCT = "struct"
|
|
235
|
+
NULL = "null"
|
|
223
236
|
|
|
224
237
|
# GRANULAR
|
|
225
238
|
UNIX_SECONDS = "unix_seconds"
|
|
@@ -960,6 +973,10 @@ class ColumnAssignment(BaseModel):
|
|
|
960
973
|
def is_complete(self) -> bool:
|
|
961
974
|
return Modifier.PARTIAL not in self.modifiers
|
|
962
975
|
|
|
976
|
+
@property
|
|
977
|
+
def is_nullable(self) -> bool:
|
|
978
|
+
return Modifier.NULLABLE in self.modifiers
|
|
979
|
+
|
|
963
980
|
def with_namespace(self, namespace: str) -> "ColumnAssignment":
|
|
964
981
|
return ColumnAssignment(
|
|
965
982
|
alias=(
|
|
@@ -1065,9 +1082,12 @@ class Function(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
1065
1082
|
]
|
|
1066
1083
|
]
|
|
1067
1084
|
|
|
1068
|
-
def
|
|
1085
|
+
def __repr__(self):
|
|
1069
1086
|
return f'{self.operator.value}({",".join([str(a) for a in self.arguments])})'
|
|
1070
1087
|
|
|
1088
|
+
def __str__(self):
|
|
1089
|
+
return self.__repr__()
|
|
1090
|
+
|
|
1071
1091
|
@property
|
|
1072
1092
|
def datatype(self):
|
|
1073
1093
|
return self.output_datatype
|
|
@@ -2072,6 +2092,10 @@ class Datasource(Namespaced, BaseModel):
|
|
|
2072
2092
|
def full_concepts(self) -> List[Concept]:
|
|
2073
2093
|
return [c.concept for c in self.columns if Modifier.PARTIAL not in c.modifiers]
|
|
2074
2094
|
|
|
2095
|
+
@property
|
|
2096
|
+
def nullable_concepts(self) -> List[Concept]:
|
|
2097
|
+
return [c.concept for c in self.columns if Modifier.NULLABLE in c.modifiers]
|
|
2098
|
+
|
|
2075
2099
|
@property
|
|
2076
2100
|
def output_concepts(self) -> List[Concept]:
|
|
2077
2101
|
return self.concepts
|
|
@@ -2135,13 +2159,27 @@ class InstantiatedUnnestJoin(BaseModel):
|
|
|
2135
2159
|
alias: str = "unnest"
|
|
2136
2160
|
|
|
2137
2161
|
|
|
2162
|
+
class ConceptPair(BaseModel):
|
|
2163
|
+
left: Concept
|
|
2164
|
+
right: Concept
|
|
2165
|
+
modifiers: List[Modifier] = Field(default_factory=list)
|
|
2166
|
+
|
|
2167
|
+
@property
|
|
2168
|
+
def is_partial(self):
|
|
2169
|
+
return Modifier.PARTIAL in self.modifiers
|
|
2170
|
+
|
|
2171
|
+
@property
|
|
2172
|
+
def is_nullable(self):
|
|
2173
|
+
return Modifier.NULLABLE in self.modifiers
|
|
2174
|
+
|
|
2175
|
+
|
|
2138
2176
|
class BaseJoin(BaseModel):
|
|
2139
2177
|
left_datasource: Union[Datasource, "QueryDatasource"]
|
|
2140
2178
|
right_datasource: Union[Datasource, "QueryDatasource"]
|
|
2141
2179
|
concepts: List[Concept]
|
|
2142
2180
|
join_type: JoinType
|
|
2143
2181
|
filter_to_mutual: bool = False
|
|
2144
|
-
concept_pairs: list[
|
|
2182
|
+
concept_pairs: list[ConceptPair] | None = None
|
|
2145
2183
|
|
|
2146
2184
|
def __init__(self, **data: Any):
|
|
2147
2185
|
super().__init__(**data)
|
|
@@ -2219,7 +2257,7 @@ class BaseJoin(BaseModel):
|
|
|
2219
2257
|
return (
|
|
2220
2258
|
f"{self.join_type.value} JOIN {self.left_datasource.identifier} and"
|
|
2221
2259
|
f" {self.right_datasource.identifier} on"
|
|
2222
|
-
f" {','.join([str(k
|
|
2260
|
+
f" {','.join([str(k.left)+'='+str(k.right) for k in self.concept_pairs])}"
|
|
2223
2261
|
)
|
|
2224
2262
|
return (
|
|
2225
2263
|
f"{self.join_type.value} JOIN {self.left_datasource.identifier} and"
|
|
@@ -2243,8 +2281,9 @@ class QueryDatasource(BaseModel):
|
|
|
2243
2281
|
filter_concepts: List[Concept] = Field(default_factory=list)
|
|
2244
2282
|
source_type: SourceType = SourceType.SELECT
|
|
2245
2283
|
partial_concepts: List[Concept] = Field(default_factory=list)
|
|
2246
|
-
join_derived_concepts: List[Concept] = Field(default_factory=list)
|
|
2247
2284
|
hidden_concepts: List[Concept] = Field(default_factory=list)
|
|
2285
|
+
nullable_concepts: List[Concept] = Field(default_factory=list)
|
|
2286
|
+
join_derived_concepts: List[Concept] = Field(default_factory=list)
|
|
2248
2287
|
force_group: bool | None = None
|
|
2249
2288
|
existence_source_map: Dict[str, Set[Union[Datasource, "QueryDatasource"]]] = Field(
|
|
2250
2289
|
default_factory=dict
|
|
@@ -2264,6 +2303,7 @@ class QueryDatasource(BaseModel):
|
|
|
2264
2303
|
@field_validator("joins")
|
|
2265
2304
|
@classmethod
|
|
2266
2305
|
def validate_joins(cls, v):
|
|
2306
|
+
unique_pairs = set()
|
|
2267
2307
|
for join in v:
|
|
2268
2308
|
if not isinstance(join, BaseJoin):
|
|
2269
2309
|
continue
|
|
@@ -2271,7 +2311,16 @@ class QueryDatasource(BaseModel):
|
|
|
2271
2311
|
raise SyntaxError(
|
|
2272
2312
|
f"Cannot join a datasource to itself, joining {join.left_datasource}"
|
|
2273
2313
|
)
|
|
2274
|
-
|
|
2314
|
+
pairing = "".join(
|
|
2315
|
+
sorted(
|
|
2316
|
+
[join.left_datasource.identifier, join.right_datasource.identifier]
|
|
2317
|
+
)
|
|
2318
|
+
)
|
|
2319
|
+
if pairing in unique_pairs:
|
|
2320
|
+
raise SyntaxError(
|
|
2321
|
+
f"Duplicate join {join.left_datasource.identifier} and {join.right_datasource.identifier}"
|
|
2322
|
+
)
|
|
2323
|
+
unique_pairs.add(pairing)
|
|
2275
2324
|
return v
|
|
2276
2325
|
|
|
2277
2326
|
@field_validator("input_concepts")
|
|
@@ -2386,6 +2435,11 @@ class QueryDatasource(BaseModel):
|
|
|
2386
2435
|
final_source_map[key] = other.source_map[key]
|
|
2387
2436
|
for k, v in final_source_map.items():
|
|
2388
2437
|
final_source_map[k] = set(merged_datasources[x.full_name] for x in list(v))
|
|
2438
|
+
self_hidden = self.hidden_concepts or []
|
|
2439
|
+
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
|
+
]
|
|
2389
2443
|
qds = QueryDatasource(
|
|
2390
2444
|
input_concepts=unique(
|
|
2391
2445
|
self.input_concepts + other.input_concepts, "address"
|
|
@@ -2409,9 +2463,7 @@ class QueryDatasource(BaseModel):
|
|
|
2409
2463
|
),
|
|
2410
2464
|
join_derived_concepts=self.join_derived_concepts,
|
|
2411
2465
|
force_group=self.force_group,
|
|
2412
|
-
hidden_concepts=
|
|
2413
|
-
self.hidden_concepts + other.hidden_concepts, "address"
|
|
2414
|
-
),
|
|
2466
|
+
hidden_concepts=hidden,
|
|
2415
2467
|
)
|
|
2416
2468
|
|
|
2417
2469
|
return qds
|
|
@@ -2487,6 +2539,7 @@ class CTE(BaseModel):
|
|
|
2487
2539
|
joins: List[Union["Join", "InstantiatedUnnestJoin"]] = Field(default_factory=list)
|
|
2488
2540
|
condition: Optional[Union["Conditional", "Comparison", "Parenthetical"]] = None
|
|
2489
2541
|
partial_concepts: List[Concept] = Field(default_factory=list)
|
|
2542
|
+
nullable_concepts: List[Concept] = Field(default_factory=list)
|
|
2490
2543
|
join_derived_concepts: List[Concept] = Field(default_factory=list)
|
|
2491
2544
|
hidden_concepts: List[Concept] = Field(default_factory=list)
|
|
2492
2545
|
order_by: Optional[OrderBy] = None
|
|
@@ -2557,6 +2610,7 @@ class CTE(BaseModel):
|
|
|
2557
2610
|
@property
|
|
2558
2611
|
def comment(self) -> str:
|
|
2559
2612
|
base = f"Target: {str(self.grain)}."
|
|
2613
|
+
base += f" Source: {self.source.source_type}."
|
|
2560
2614
|
if self.parent_ctes:
|
|
2561
2615
|
base += f" References: {', '.join([x.name for x in self.parent_ctes])}."
|
|
2562
2616
|
if self.joins:
|
|
@@ -2565,6 +2619,15 @@ class CTE(BaseModel):
|
|
|
2565
2619
|
base += (
|
|
2566
2620
|
f"\n-- Partials: {', '.join([str(x) for x in self.partial_concepts])}."
|
|
2567
2621
|
)
|
|
2622
|
+
base += f"\n-- Source Map: {self.source_map}."
|
|
2623
|
+
base += f"\n-- Output: {', '.join([str(x) for x in self.output_columns])}."
|
|
2624
|
+
if self.hidden_concepts:
|
|
2625
|
+
base += f"\n-- Hidden: {', '.join([str(x) for x in self.hidden_concepts])}."
|
|
2626
|
+
if self.nullable_concepts:
|
|
2627
|
+
base += (
|
|
2628
|
+
f"\n-- Nullable: {', '.join([str(x) for x in self.nullable_concepts])}."
|
|
2629
|
+
)
|
|
2630
|
+
|
|
2568
2631
|
return base
|
|
2569
2632
|
|
|
2570
2633
|
def inline_parent_datasource(self, parent: CTE, force_group: bool = False) -> bool:
|
|
@@ -2632,6 +2695,10 @@ class CTE(BaseModel):
|
|
|
2632
2695
|
f" {self.name} {other.name} conditions {self.condition} {other.condition}"
|
|
2633
2696
|
)
|
|
2634
2697
|
raise ValueError(error)
|
|
2698
|
+
mutually_hidden = []
|
|
2699
|
+
for concept in self.hidden_concepts:
|
|
2700
|
+
if concept in other.hidden_concepts:
|
|
2701
|
+
mutually_hidden.append(concept)
|
|
2635
2702
|
self.partial_concepts = unique(
|
|
2636
2703
|
self.partial_concepts + other.partial_concepts, "address"
|
|
2637
2704
|
)
|
|
@@ -2654,9 +2721,10 @@ class CTE(BaseModel):
|
|
|
2654
2721
|
self.source.output_concepts = unique(
|
|
2655
2722
|
self.source.output_concepts + other.source.output_concepts, "address"
|
|
2656
2723
|
)
|
|
2657
|
-
self.
|
|
2658
|
-
self.
|
|
2724
|
+
self.nullable_concepts = unique(
|
|
2725
|
+
self.nullable_concepts + other.nullable_concepts, "address"
|
|
2659
2726
|
)
|
|
2727
|
+
self.hidden_concepts = mutually_hidden
|
|
2660
2728
|
self.existence_source_map = {
|
|
2661
2729
|
**self.existence_source_map,
|
|
2662
2730
|
**other.existence_source_map,
|
|
@@ -2821,7 +2889,7 @@ class Join(BaseModel):
|
|
|
2821
2889
|
right_cte: CTE | Datasource
|
|
2822
2890
|
jointype: JoinType
|
|
2823
2891
|
joinkeys: List[JoinKey]
|
|
2824
|
-
joinkey_pairs: List[
|
|
2892
|
+
joinkey_pairs: List[ConceptPair] | None = None
|
|
2825
2893
|
|
|
2826
2894
|
@property
|
|
2827
2895
|
def left_name(self) -> str:
|
|
@@ -2856,7 +2924,7 @@ class Join(BaseModel):
|
|
|
2856
2924
|
return (
|
|
2857
2925
|
f"{self.jointype.value} JOIN {self.left_name} and"
|
|
2858
2926
|
f" {self.right_name} on"
|
|
2859
|
-
f" {','.join([str(k
|
|
2927
|
+
f" {','.join([str(k.left)+'='+str(k.right)+str(k.modifiers) for k in self.joinkey_pairs])}"
|
|
2860
2928
|
)
|
|
2861
2929
|
return (
|
|
2862
2930
|
f"{self.jointype.value} JOIN {self.left_name} and"
|
|
@@ -3012,6 +3080,9 @@ class EnvironmentDatasourceDict(dict):
|
|
|
3012
3080
|
def values(self) -> ValuesView[Datasource]: # type: ignore
|
|
3013
3081
|
return super().values()
|
|
3014
3082
|
|
|
3083
|
+
def items(self) -> ItemsView[str, Datasource]: # type: ignore
|
|
3084
|
+
return super().items()
|
|
3085
|
+
|
|
3015
3086
|
|
|
3016
3087
|
class EnvironmentConceptDict(dict):
|
|
3017
3088
|
def __init__(self, *args, **kwargs) -> None:
|
|
@@ -3426,11 +3497,41 @@ class Comparison(
|
|
|
3426
3497
|
]
|
|
3427
3498
|
operator: ComparisonOperator
|
|
3428
3499
|
|
|
3429
|
-
def
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3500
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
3501
|
+
super().__init__(*args, **kwargs)
|
|
3502
|
+
if self.operator in (ComparisonOperator.IS, ComparisonOperator.IS_NOT):
|
|
3503
|
+
if self.right != MagicConstants.NULL and DataType.BOOL != arg_to_datatype(
|
|
3504
|
+
self.right
|
|
3505
|
+
):
|
|
3506
|
+
raise SyntaxError(
|
|
3507
|
+
f"Cannot use {self.operator.value} with non-null or boolean value {self.right}"
|
|
3508
|
+
)
|
|
3509
|
+
elif self.operator in (ComparisonOperator.IN, ComparisonOperator.NOT_IN):
|
|
3510
|
+
right = arg_to_datatype(self.right)
|
|
3511
|
+
if not isinstance(self.right, Concept) and not isinstance(right, ListType):
|
|
3512
|
+
raise SyntaxError(
|
|
3513
|
+
f"Cannot use {self.operator.value} with non-list type {right} in {str(self)}"
|
|
3514
|
+
)
|
|
3515
|
+
|
|
3516
|
+
elif isinstance(right, ListType) and not is_compatible_datatype(
|
|
3517
|
+
arg_to_datatype(self.left), right.value_data_type
|
|
3518
|
+
):
|
|
3519
|
+
raise SyntaxError(
|
|
3520
|
+
f"Cannot compare {arg_to_datatype(self.left)} and {right} with operator {self.operator} in {str(self)}"
|
|
3521
|
+
)
|
|
3522
|
+
elif isinstance(self.right, Concept) and not is_compatible_datatype(
|
|
3523
|
+
arg_to_datatype(self.left), arg_to_datatype(self.right)
|
|
3524
|
+
):
|
|
3525
|
+
raise SyntaxError(
|
|
3526
|
+
f"Cannot compare {arg_to_datatype(self.left)} and {arg_to_datatype(self.right)} with operator {self.operator} in {str(self)}"
|
|
3527
|
+
)
|
|
3528
|
+
else:
|
|
3529
|
+
if not is_compatible_datatype(
|
|
3530
|
+
arg_to_datatype(self.left), arg_to_datatype(self.right)
|
|
3531
|
+
):
|
|
3532
|
+
raise SyntaxError(
|
|
3533
|
+
f"Cannot compare {arg_to_datatype(self.left)} and {arg_to_datatype(self.right)} of different types with operator {self.operator} in {str(self)}"
|
|
3534
|
+
)
|
|
3434
3535
|
|
|
3435
3536
|
def __add__(self, other):
|
|
3436
3537
|
if other is None:
|
|
@@ -3631,6 +3732,12 @@ class CaseWhen(Namespaced, SelectContext, BaseModel):
|
|
|
3631
3732
|
comparison: Conditional | SubselectComparison | Comparison
|
|
3632
3733
|
expr: "Expr"
|
|
3633
3734
|
|
|
3735
|
+
def __str__(self):
|
|
3736
|
+
return self.__repr__()
|
|
3737
|
+
|
|
3738
|
+
def __repr__(self):
|
|
3739
|
+
return f"WHEN {str(self.comparison)} THEN {str(self.expr)}"
|
|
3740
|
+
|
|
3634
3741
|
@property
|
|
3635
3742
|
def concept_arguments(self):
|
|
3636
3743
|
return get_concept_arguments(self.comparison) + get_concept_arguments(self.expr)
|
|
@@ -4323,6 +4430,7 @@ class ShowStatement(BaseModel):
|
|
|
4323
4430
|
|
|
4324
4431
|
Expr = (
|
|
4325
4432
|
bool
|
|
4433
|
+
| MagicConstants
|
|
4326
4434
|
| int
|
|
4327
4435
|
| str
|
|
4328
4436
|
| float
|
|
@@ -4377,9 +4485,34 @@ def dict_to_map_wrapper(arg):
|
|
|
4377
4485
|
return MapWrapper(arg, key_type=key_types[0], value_type=value_types[0])
|
|
4378
4486
|
|
|
4379
4487
|
|
|
4488
|
+
def merge_datatypes(
|
|
4489
|
+
inputs: list[DataType | ListType | StructType | MapType | NumericType],
|
|
4490
|
+
) -> DataType | ListType | StructType | MapType | NumericType:
|
|
4491
|
+
"""This is a temporary hack for doing between
|
|
4492
|
+
allowable datatype transformation matrix"""
|
|
4493
|
+
if len(inputs) == 1:
|
|
4494
|
+
return inputs[0]
|
|
4495
|
+
if set(inputs) == {DataType.INTEGER, DataType.FLOAT}:
|
|
4496
|
+
return DataType.FLOAT
|
|
4497
|
+
if set(inputs) == {DataType.INTEGER, DataType.NUMERIC}:
|
|
4498
|
+
return DataType.NUMERIC
|
|
4499
|
+
if any(isinstance(x, NumericType) for x in inputs) and all(
|
|
4500
|
+
isinstance(x, NumericType)
|
|
4501
|
+
or x in (DataType.INTEGER, DataType.FLOAT, DataType.NUMERIC)
|
|
4502
|
+
for x in inputs
|
|
4503
|
+
):
|
|
4504
|
+
candidate = next(x for x in inputs if isinstance(x, NumericType))
|
|
4505
|
+
return candidate
|
|
4506
|
+
return inputs[0]
|
|
4507
|
+
|
|
4508
|
+
|
|
4380
4509
|
def arg_to_datatype(arg) -> DataType | ListType | StructType | MapType | NumericType:
|
|
4381
4510
|
if isinstance(arg, Function):
|
|
4382
4511
|
return arg.output_datatype
|
|
4512
|
+
elif isinstance(arg, MagicConstants):
|
|
4513
|
+
if arg == MagicConstants.NULL:
|
|
4514
|
+
return DataType.NULL
|
|
4515
|
+
raise ValueError(f"Cannot parse arg datatype for arg of type {arg}")
|
|
4383
4516
|
elif isinstance(arg, Concept):
|
|
4384
4517
|
return arg.datatype
|
|
4385
4518
|
elif isinstance(arg, bool):
|
|
@@ -135,7 +135,7 @@ class PredicatePushdown(OptimizationRule):
|
|
|
135
135
|
f"Skipping {candidate} as not a basic [no aggregate, etc] condition"
|
|
136
136
|
)
|
|
137
137
|
continue
|
|
138
|
-
self.
|
|
138
|
+
self.debug(
|
|
139
139
|
f"Checking candidate {candidate}, {type(candidate)}, scalar: {is_scalar_condition(candidate)}"
|
|
140
140
|
)
|
|
141
141
|
for parent_cte in cte.parent_ctes:
|
|
@@ -10,6 +10,7 @@ from trilogy.core.processing.node_generators.common import (
|
|
|
10
10
|
)
|
|
11
11
|
from trilogy.utility import unique
|
|
12
12
|
from trilogy.constants import logger
|
|
13
|
+
from trilogy.core.enums import SourceType
|
|
13
14
|
from itertools import combinations
|
|
14
15
|
|
|
15
16
|
LOGGER_PREFIX = "[GEN_BASIC_NODE]"
|
|
@@ -35,7 +36,11 @@ def gen_basic_node(
|
|
|
35
36
|
attempts: List[tuple[list[Concept], list[Concept]]] = [
|
|
36
37
|
(parent_concepts, [concept] + local_optional_redundant)
|
|
37
38
|
]
|
|
38
|
-
equivalent_optional = [
|
|
39
|
+
equivalent_optional = [
|
|
40
|
+
x
|
|
41
|
+
for x in local_optional
|
|
42
|
+
if x.lineage == concept.lineage and x.address != concept.address
|
|
43
|
+
]
|
|
39
44
|
non_equivalent_optional = [
|
|
40
45
|
x for x in local_optional if x not in equivalent_optional
|
|
41
46
|
]
|
|
@@ -61,8 +66,10 @@ def gen_basic_node(
|
|
|
61
66
|
depth=depth + 1,
|
|
62
67
|
history=history,
|
|
63
68
|
)
|
|
69
|
+
|
|
64
70
|
if not parent_node:
|
|
65
71
|
continue
|
|
72
|
+
parent_node.source_type = SourceType.BASIC
|
|
66
73
|
parents: List[StrategyNode] = [parent_node]
|
|
67
74
|
for x in basic_output:
|
|
68
75
|
sources = [p for p in parents if x in p.output_concepts]
|