pytrilogy 0.0.2.12__py3-none-any.whl → 0.0.2.13__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.13.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.2.12.dist-info → pytrilogy-0.0.2.13.dist-info}/RECORD +19 -19
- {pytrilogy-0.0.2.12.dist-info → pytrilogy-0.0.2.13.dist-info}/WHEEL +1 -1
- trilogy/__init__.py +1 -1
- trilogy/constants.py +5 -0
- trilogy/core/enums.py +3 -0
- trilogy/core/models.py +31 -7
- trilogy/core/processing/node_generators/basic_node.py +8 -1
- trilogy/core/processing/node_generators/common.py +13 -23
- trilogy/core/processing/nodes/base_node.py +12 -10
- trilogy/core/processing/nodes/merge_node.py +1 -1
- trilogy/core/query_processor.py +5 -10
- trilogy/executor.py +8 -2
- trilogy/parsing/common.py +16 -2
- trilogy/parsing/parse_engine.py +18 -10
- trilogy/parsing/trilogy.lark +3 -3
- {pytrilogy-0.0.2.12.dist-info → pytrilogy-0.0.2.13.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.12.dist-info → pytrilogy-0.0.2.13.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.2.12.dist-info → pytrilogy-0.0.2.13.dist-info}/top_level.txt +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
trilogy/__init__.py,sha256=
|
|
1
|
+
trilogy/__init__.py,sha256=U1mVKIIGVmjHu6kwRMyNhb3buZHWWzbk-c9Hu7FiBGQ,291
|
|
2
2
|
trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
trilogy/constants.py,sha256=
|
|
3
|
+
trilogy/constants.py,sha256=rZJh3fAx3ljxf_QZNECR-devR6QXkYc9mpLCxIWNqB0,960
|
|
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,9 +16,9 @@ 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=9-1kYfq4NqAthe50Jgs0WjBnZtEWJw0ReCBfFhVxD0Y,144799
|
|
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=JJFBVBmT5QNsJ9rSDKNJBINLm7YW7i5fjAp98H0Wcd8,19281
|
|
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
|
|
@@ -29,8 +29,8 @@ trilogy/core/processing/concept_strategies_v3.py,sha256=ae6FmwiKNiEbOU2GhnzggFMh
|
|
|
29
29
|
trilogy/core/processing/graph_utils.py,sha256=aq-kqk4Iado2HywDxWEejWc-7PGO6Oa-ZQLAM6XWPHw,1199
|
|
30
30
|
trilogy/core/processing/utility.py,sha256=QKaZL5yJzGJBWCirgB1cAKgcDOibhyk7ETvHveb3GOE,14604
|
|
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=
|
|
32
|
+
trilogy/core/processing/node_generators/basic_node.py,sha256=IHj5jEloUe5yojGRLAzt35FcfHqGviWQdS8ETyvr39Q,3292
|
|
33
|
+
trilogy/core/processing/node_generators/common.py,sha256=WY41zjxSCG13n3mdUHmcE0mUQ0gtWxz4nugBgxHONd8,9327
|
|
34
34
|
trilogy/core/processing/node_generators/filter_node.py,sha256=Ij2WqyOsu-TFxhAcL50PLMGpghsSWXJnWEJ8yTqOwrY,8228
|
|
35
35
|
trilogy/core/processing/node_generators/group_node.py,sha256=Du-9uFXD0M-aHq2MV7v5R3QCrAL0JZBFMW-YQwgb6Bw,3135
|
|
36
36
|
trilogy/core/processing/node_generators/group_to_node.py,sha256=nzITnhaALIT7FMonyo16nNo-kSrLfefa9sZBYecrvkU,2887
|
|
@@ -41,10 +41,10 @@ trilogy/core/processing/node_generators/select_node.py,sha256=E8bKOAUpwLwZy1iiaF
|
|
|
41
41
|
trilogy/core/processing/node_generators/unnest_node.py,sha256=aZeixbOzMtXi7BPahKr9bOkIhTciyD9Klsj0kZ56F6s,2189
|
|
42
42
|
trilogy/core/processing/node_generators/window_node.py,sha256=lFfmEjX_mLB7MuOM6CuKNnks1CabokGImpwhbQzjnkE,3283
|
|
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=P4VyOhbgPgTLaXEIftbVVmmEPviScJJhi9v6hSxjC7M,13155
|
|
45
45
|
trilogy/core/processing/nodes/filter_node.py,sha256=DBOSGFfkiILrZa1BlLv2uxUSkgWtSIKiZplqyKXPjg8,2132
|
|
46
46
|
trilogy/core/processing/nodes/group_node.py,sha256=wE6tgyCUL74v76O8jACDm4oYMov4dAlwzLa5xMYReAA,6294
|
|
47
|
-
trilogy/core/processing/nodes/merge_node.py,sha256=
|
|
47
|
+
trilogy/core/processing/nodes/merge_node.py,sha256=KOTdYli_T3c5RwMaK_73W5UNtKPA0F8hjDJF9jQx2fs,14491
|
|
48
48
|
trilogy/core/processing/nodes/select_node_v2.py,sha256=QuXNcwgjTRYamOoIooGrp4ie6INcqA9whtC5LZWjD8s,7180
|
|
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
|
|
@@ -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=fa3R0xHXQkU8BUN8quPe7qCzez6qaRbnctkYaLgHLxY,8863
|
|
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=yCHd6RVRijNctZJZ3iRJG2263UZL2n8EoFA_-Qfr88E,62966
|
|
73
73
|
trilogy/parsing/render.py,sha256=Gy_6wVYPwYLf35Iota08sbqveuWILtUhI8MYStcvtJM,12174
|
|
74
|
-
trilogy/parsing/trilogy.lark,sha256=
|
|
74
|
+
trilogy/parsing/trilogy.lark,sha256=00j0D77gpTBsyet9WGN4Ir9Nc-YvNwXFr2toRiASb_M,11525
|
|
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.13.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
78
|
+
pytrilogy-0.0.2.13.dist-info/METADATA,sha256=AJXWyDVSN9EhYPYJlrkXhMVULqg3HyscbZGDhgVd-FA,7907
|
|
79
|
+
pytrilogy-0.0.2.13.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
80
|
+
pytrilogy-0.0.2.13.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
|
|
81
|
+
pytrilogy-0.0.2.13.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
82
|
+
pytrilogy-0.0.2.13.dist-info/RECORD,,
|
trilogy/__init__.py
CHANGED
trilogy/constants.py
CHANGED
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
|
@@ -2264,6 +2264,7 @@ class QueryDatasource(BaseModel):
|
|
|
2264
2264
|
@field_validator("joins")
|
|
2265
2265
|
@classmethod
|
|
2266
2266
|
def validate_joins(cls, v):
|
|
2267
|
+
unique_pairs = set()
|
|
2267
2268
|
for join in v:
|
|
2268
2269
|
if not isinstance(join, BaseJoin):
|
|
2269
2270
|
continue
|
|
@@ -2271,7 +2272,16 @@ class QueryDatasource(BaseModel):
|
|
|
2271
2272
|
raise SyntaxError(
|
|
2272
2273
|
f"Cannot join a datasource to itself, joining {join.left_datasource}"
|
|
2273
2274
|
)
|
|
2274
|
-
|
|
2275
|
+
pairing = "".join(
|
|
2276
|
+
sorted(
|
|
2277
|
+
[join.left_datasource.identifier, join.right_datasource.identifier]
|
|
2278
|
+
)
|
|
2279
|
+
)
|
|
2280
|
+
if pairing in unique_pairs:
|
|
2281
|
+
raise SyntaxError(
|
|
2282
|
+
f"Duplicate join {join.left_datasource.identifier} and {join.right_datasource.identifier}"
|
|
2283
|
+
)
|
|
2284
|
+
unique_pairs.add(pairing)
|
|
2275
2285
|
return v
|
|
2276
2286
|
|
|
2277
2287
|
@field_validator("input_concepts")
|
|
@@ -2386,6 +2396,11 @@ class QueryDatasource(BaseModel):
|
|
|
2386
2396
|
final_source_map[key] = other.source_map[key]
|
|
2387
2397
|
for k, v in final_source_map.items():
|
|
2388
2398
|
final_source_map[k] = set(merged_datasources[x.full_name] for x in list(v))
|
|
2399
|
+
self_hidden = self.hidden_concepts or []
|
|
2400
|
+
other_hidden = other.hidden_concepts or []
|
|
2401
|
+
hidden = [
|
|
2402
|
+
x for x in self_hidden if x.address in [y.address for y in other_hidden]
|
|
2403
|
+
]
|
|
2389
2404
|
qds = QueryDatasource(
|
|
2390
2405
|
input_concepts=unique(
|
|
2391
2406
|
self.input_concepts + other.input_concepts, "address"
|
|
@@ -2409,9 +2424,7 @@ class QueryDatasource(BaseModel):
|
|
|
2409
2424
|
),
|
|
2410
2425
|
join_derived_concepts=self.join_derived_concepts,
|
|
2411
2426
|
force_group=self.force_group,
|
|
2412
|
-
hidden_concepts=
|
|
2413
|
-
self.hidden_concepts + other.hidden_concepts, "address"
|
|
2414
|
-
),
|
|
2427
|
+
hidden_concepts=hidden,
|
|
2415
2428
|
)
|
|
2416
2429
|
|
|
2417
2430
|
return qds
|
|
@@ -2557,6 +2570,7 @@ class CTE(BaseModel):
|
|
|
2557
2570
|
@property
|
|
2558
2571
|
def comment(self) -> str:
|
|
2559
2572
|
base = f"Target: {str(self.grain)}."
|
|
2573
|
+
base += f" Source: {self.source.source_type}."
|
|
2560
2574
|
if self.parent_ctes:
|
|
2561
2575
|
base += f" References: {', '.join([x.name for x in self.parent_ctes])}."
|
|
2562
2576
|
if self.joins:
|
|
@@ -2565,6 +2579,11 @@ class CTE(BaseModel):
|
|
|
2565
2579
|
base += (
|
|
2566
2580
|
f"\n-- Partials: {', '.join([str(x) for x in self.partial_concepts])}."
|
|
2567
2581
|
)
|
|
2582
|
+
base += f"\n-- Source Map: {self.source_map}."
|
|
2583
|
+
base += f"\n-- Output: {', '.join([str(x) for x in self.output_columns])}."
|
|
2584
|
+
if self.hidden_concepts:
|
|
2585
|
+
base += f"\n-- Hidden: {', '.join([str(x) for x in self.hidden_concepts])}."
|
|
2586
|
+
|
|
2568
2587
|
return base
|
|
2569
2588
|
|
|
2570
2589
|
def inline_parent_datasource(self, parent: CTE, force_group: bool = False) -> bool:
|
|
@@ -2632,6 +2651,10 @@ class CTE(BaseModel):
|
|
|
2632
2651
|
f" {self.name} {other.name} conditions {self.condition} {other.condition}"
|
|
2633
2652
|
)
|
|
2634
2653
|
raise ValueError(error)
|
|
2654
|
+
mutually_hidden = []
|
|
2655
|
+
for concept in self.hidden_concepts:
|
|
2656
|
+
if concept in other.hidden_concepts:
|
|
2657
|
+
mutually_hidden.append(concept)
|
|
2635
2658
|
self.partial_concepts = unique(
|
|
2636
2659
|
self.partial_concepts + other.partial_concepts, "address"
|
|
2637
2660
|
)
|
|
@@ -2654,9 +2677,7 @@ class CTE(BaseModel):
|
|
|
2654
2677
|
self.source.output_concepts = unique(
|
|
2655
2678
|
self.source.output_concepts + other.source.output_concepts, "address"
|
|
2656
2679
|
)
|
|
2657
|
-
self.hidden_concepts =
|
|
2658
|
-
self.hidden_concepts + other.hidden_concepts, "address"
|
|
2659
|
-
)
|
|
2680
|
+
self.hidden_concepts = mutually_hidden
|
|
2660
2681
|
self.existence_source_map = {
|
|
2661
2682
|
**self.existence_source_map,
|
|
2662
2683
|
**other.existence_source_map,
|
|
@@ -3012,6 +3033,9 @@ class EnvironmentDatasourceDict(dict):
|
|
|
3012
3033
|
def values(self) -> ValuesView[Datasource]: # type: ignore
|
|
3013
3034
|
return super().values()
|
|
3014
3035
|
|
|
3036
|
+
def items(self) -> ItemsView[str, Datasource]: # type: ignore
|
|
3037
|
+
return super().items()
|
|
3038
|
+
|
|
3015
3039
|
|
|
3016
3040
|
class EnvironmentConceptDict(dict):
|
|
3017
3041
|
def __init__(self, *args, **kwargs) -> None:
|
|
@@ -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]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import List, Tuple
|
|
1
|
+
from typing import List, Tuple, Callable
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
from trilogy.core.enums import PurposeLineage, Purpose
|
|
@@ -96,6 +96,7 @@ def gen_property_enrichment_node(
|
|
|
96
96
|
g,
|
|
97
97
|
depth: int,
|
|
98
98
|
source_concepts,
|
|
99
|
+
log_lambda: Callable,
|
|
99
100
|
history: History | None = None,
|
|
100
101
|
conditions: WhereClause | None = None,
|
|
101
102
|
):
|
|
@@ -106,8 +107,8 @@ def gen_property_enrichment_node(
|
|
|
106
107
|
keys = "-".join([y.address for y in x.keys])
|
|
107
108
|
required_keys[keys].add(x.address)
|
|
108
109
|
final_nodes = []
|
|
109
|
-
node_joins = []
|
|
110
110
|
for _k, vs in required_keys.items():
|
|
111
|
+
log_lambda(f"Generating enrichment node for {_k} with {vs}")
|
|
111
112
|
ks = _k.split("-")
|
|
112
113
|
enrich_node: StrategyNode = source_concepts(
|
|
113
114
|
mandatory_list=[environment.concepts[k] for k in ks]
|
|
@@ -119,17 +120,6 @@ def gen_property_enrichment_node(
|
|
|
119
120
|
conditions=conditions,
|
|
120
121
|
)
|
|
121
122
|
final_nodes.append(enrich_node)
|
|
122
|
-
node_joins.append(
|
|
123
|
-
NodeJoin(
|
|
124
|
-
left_node=enrich_node,
|
|
125
|
-
right_node=base_node,
|
|
126
|
-
concepts=concept_to_relevant_joins(
|
|
127
|
-
[environment.concepts[k] for k in ks]
|
|
128
|
-
),
|
|
129
|
-
filter_to_mutual=False,
|
|
130
|
-
join_type=JoinType.LEFT_OUTER,
|
|
131
|
-
)
|
|
132
|
-
)
|
|
133
123
|
return MergeNode(
|
|
134
124
|
input_concepts=unique(
|
|
135
125
|
base_node.output_concepts
|
|
@@ -146,9 +136,8 @@ def gen_property_enrichment_node(
|
|
|
146
136
|
g=g,
|
|
147
137
|
parents=[
|
|
148
138
|
base_node,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
node_joins=node_joins,
|
|
139
|
+
]
|
|
140
|
+
+ final_nodes,
|
|
152
141
|
)
|
|
153
142
|
|
|
154
143
|
|
|
@@ -197,6 +186,7 @@ def gen_enrichment_node(
|
|
|
197
186
|
source_concepts,
|
|
198
187
|
history=history,
|
|
199
188
|
conditions=conditions,
|
|
189
|
+
log_lambda=log_lambda,
|
|
200
190
|
)
|
|
201
191
|
|
|
202
192
|
enrich_node: StrategyNode = source_concepts( # this fetches the parent + join keys
|
|
@@ -216,14 +206,14 @@ def gen_enrichment_node(
|
|
|
216
206
|
log_lambda(
|
|
217
207
|
f"{str(type(base_node).__name__)} returning merge node with group node + enrichment node"
|
|
218
208
|
)
|
|
219
|
-
|
|
209
|
+
non_hidden = [
|
|
210
|
+
x
|
|
211
|
+
for x in base_node.output_concepts
|
|
212
|
+
if x.address not in [y.address for y in base_node.hidden_concepts]
|
|
213
|
+
]
|
|
220
214
|
return MergeNode(
|
|
221
|
-
input_concepts=unique(
|
|
222
|
-
|
|
223
|
-
),
|
|
224
|
-
output_concepts=unique(
|
|
225
|
-
join_keys + extra_required + base_node.output_concepts, "address"
|
|
226
|
-
),
|
|
215
|
+
input_concepts=unique(join_keys + extra_required + non_hidden, "address"),
|
|
216
|
+
output_concepts=unique(join_keys + extra_required + non_hidden, "address"),
|
|
227
217
|
environment=environment,
|
|
228
218
|
g=g,
|
|
229
219
|
parents=[enrich_node, base_node],
|
|
@@ -61,11 +61,9 @@ def resolve_concept_map(
|
|
|
61
61
|
for concept in input.output_concepts:
|
|
62
62
|
if concept.address not in input.non_partial_concept_addresses:
|
|
63
63
|
continue
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
and concept.address in input.hidden_concepts
|
|
68
|
-
):
|
|
64
|
+
if isinstance(input, QueryDatasource) and concept.address in [
|
|
65
|
+
x.address for x in input.hidden_concepts
|
|
66
|
+
]:
|
|
69
67
|
continue
|
|
70
68
|
if concept.address in full_addresses:
|
|
71
69
|
|
|
@@ -82,10 +80,9 @@ def resolve_concept_map(
|
|
|
82
80
|
for concept in input.output_concepts:
|
|
83
81
|
if concept.address not in [t.address for t in inherited_inputs]:
|
|
84
82
|
continue
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
):
|
|
83
|
+
if isinstance(input, QueryDatasource) and concept.address in [
|
|
84
|
+
x.address for x in input.hidden_concepts
|
|
85
|
+
]:
|
|
89
86
|
continue
|
|
90
87
|
if len(concept_map.get(concept.address, [])) == 0:
|
|
91
88
|
concept_map[concept.address].add(input)
|
|
@@ -94,6 +91,7 @@ def resolve_concept_map(
|
|
|
94
91
|
if target.address not in inherited:
|
|
95
92
|
# an empty source means it is defined in this CTE
|
|
96
93
|
concept_map[target.address] = set()
|
|
94
|
+
|
|
97
95
|
return concept_map
|
|
98
96
|
|
|
99
97
|
|
|
@@ -226,7 +224,10 @@ class StrategyNode:
|
|
|
226
224
|
def remove_output_concepts(self, concepts: List[Concept]):
|
|
227
225
|
for x in concepts:
|
|
228
226
|
self.hidden_concepts.append(x)
|
|
229
|
-
|
|
227
|
+
addresses = [x.address for x in concepts]
|
|
228
|
+
self.output_concepts = [
|
|
229
|
+
x for x in self.output_concepts if x.address not in addresses
|
|
230
|
+
]
|
|
230
231
|
self.rebuild_cache()
|
|
231
232
|
|
|
232
233
|
@property
|
|
@@ -257,6 +258,7 @@ class StrategyNode:
|
|
|
257
258
|
targets=self.output_concepts,
|
|
258
259
|
inherited_inputs=self.input_concepts + self.existence_concepts,
|
|
259
260
|
)
|
|
261
|
+
|
|
260
262
|
return QueryDatasource(
|
|
261
263
|
input_concepts=self.input_concepts,
|
|
262
264
|
output_concepts=self.output_concepts,
|
|
@@ -371,7 +371,7 @@ class MergeNode(StrategyNode):
|
|
|
371
371
|
conditions=self.conditions,
|
|
372
372
|
hidden_concepts=list(self.hidden_concepts),
|
|
373
373
|
virtual_output_concepts=list(self.virtual_output_concepts),
|
|
374
|
-
node_joins=self.node_joins,
|
|
374
|
+
node_joins=list(self.node_joins) if self.node_joins else None,
|
|
375
375
|
join_concepts=list(self.join_concepts) if self.join_concepts else None,
|
|
376
376
|
force_join_type=self.force_join_type,
|
|
377
377
|
existence_concepts=list(self.existence_concepts),
|
trilogy/core/query_processor.py
CHANGED
|
@@ -35,7 +35,6 @@ from trilogy.core.ergonomics import CTE_NAMES
|
|
|
35
35
|
from trilogy.core.optimization import optimize_ctes
|
|
36
36
|
from math import ceil
|
|
37
37
|
from collections import defaultdict
|
|
38
|
-
from random import shuffle
|
|
39
38
|
|
|
40
39
|
LOGGER_PREFIX = "[QUERY BUILD]"
|
|
41
40
|
|
|
@@ -128,8 +127,6 @@ def generate_source_map(
|
|
|
128
127
|
if qdk in output_address:
|
|
129
128
|
source_map[qdk].append(cte.name)
|
|
130
129
|
# now do a pass that accepts partials
|
|
131
|
-
# TODO: move this into a second loop by first creationg all sub sources
|
|
132
|
-
# then loop through this
|
|
133
130
|
for cte in matches:
|
|
134
131
|
if qdk not in source_map:
|
|
135
132
|
source_map[qdk] = [cte.name]
|
|
@@ -180,7 +177,6 @@ def generate_cte_name(full_name: str, name_map: dict[str, str]) -> str:
|
|
|
180
177
|
int = ceil(idx / len(CTE_NAMES))
|
|
181
178
|
suffix = f"_{int}"
|
|
182
179
|
valid = [x for x in CTE_NAMES if x + suffix not in name_map.values()]
|
|
183
|
-
shuffle(valid)
|
|
184
180
|
lookup = valid[0]
|
|
185
181
|
new_name = f"{lookup}{suffix}"
|
|
186
182
|
name_map[full_name] = new_name
|
|
@@ -334,12 +330,13 @@ def append_existence_check(
|
|
|
334
330
|
for subselect in where.existence_arguments:
|
|
335
331
|
if not subselect:
|
|
336
332
|
continue
|
|
337
|
-
|
|
338
|
-
f"{LOGGER_PREFIX} fetching existance clause inputs {[str(c) for c in subselect]}"
|
|
339
|
-
)
|
|
333
|
+
|
|
340
334
|
eds = source_query_concepts(
|
|
341
335
|
[*subselect], environment=environment, g=graph, history=history
|
|
342
336
|
)
|
|
337
|
+
logger.info(
|
|
338
|
+
f"{LOGGER_PREFIX} fetching existence clause inputs {[str(c) for c in subselect]}"
|
|
339
|
+
)
|
|
343
340
|
node.add_parents([eds])
|
|
344
341
|
node.add_existence_concepts([*subselect])
|
|
345
342
|
|
|
@@ -384,9 +381,7 @@ def get_query_node(
|
|
|
384
381
|
if nest_where and statement.where_clause:
|
|
385
382
|
if not all_aggregate:
|
|
386
383
|
ods.conditions = statement.where_clause.conditional
|
|
387
|
-
ods.
|
|
388
|
-
# ods.hidden_concepts = where_delta
|
|
389
|
-
ods.rebuild_cache()
|
|
384
|
+
ods.set_output_concepts(statement.output_components)
|
|
390
385
|
append_existence_check(ods, environment, graph, history)
|
|
391
386
|
ds = GroupNode(
|
|
392
387
|
output_concepts=statement.output_components,
|
trilogy/executor.py
CHANGED
|
@@ -300,10 +300,16 @@ class Executor(object):
|
|
|
300
300
|
self.environment.add_datasource(x.datasource)
|
|
301
301
|
yield x
|
|
302
302
|
|
|
303
|
-
def execute_raw_sql(
|
|
303
|
+
def execute_raw_sql(
|
|
304
|
+
self, command: str, variables: dict | None = None
|
|
305
|
+
) -> CursorResult:
|
|
304
306
|
"""Run a command against the raw underlying
|
|
305
307
|
execution engine"""
|
|
306
|
-
|
|
308
|
+
if variables:
|
|
309
|
+
return self.connection.execute(text(command), variables)
|
|
310
|
+
return self.connection.execute(
|
|
311
|
+
text(command),
|
|
312
|
+
)
|
|
307
313
|
|
|
308
314
|
def execute_text(self, command: str) -> List[CursorResult]:
|
|
309
315
|
"""Run a preql text command"""
|
trilogy/parsing/common.py
CHANGED
|
@@ -50,7 +50,7 @@ def process_function_args(
|
|
|
50
50
|
id_hash = string_to_hash(str(arg))
|
|
51
51
|
concept = function_to_concept(
|
|
52
52
|
arg,
|
|
53
|
-
name=f"{VIRTUAL_CONCEPT_PREFIX}_{id_hash}",
|
|
53
|
+
name=f"{VIRTUAL_CONCEPT_PREFIX}_{arg.operator.value}_{id_hash}",
|
|
54
54
|
namespace=environment.namespace,
|
|
55
55
|
)
|
|
56
56
|
# to satisfy mypy, concept will always have metadata
|
|
@@ -255,20 +255,34 @@ def arbitrary_to_concept(
|
|
|
255
255
|
| str
|
|
256
256
|
),
|
|
257
257
|
namespace: str,
|
|
258
|
-
name: str,
|
|
258
|
+
name: str | None = None,
|
|
259
259
|
metadata: Metadata | None = None,
|
|
260
260
|
purpose: Purpose | None = None,
|
|
261
261
|
) -> Concept:
|
|
262
262
|
|
|
263
263
|
if isinstance(parent, AggregateWrapper):
|
|
264
|
+
if not name:
|
|
265
|
+
name = (
|
|
266
|
+
f"_agg_{parent.function.operator.value}_{string_to_hash(str(parent))}"
|
|
267
|
+
)
|
|
264
268
|
return agg_wrapper_to_concept(parent, namespace, name, metadata, purpose)
|
|
265
269
|
elif isinstance(parent, WindowItem):
|
|
270
|
+
if not name:
|
|
271
|
+
name = f"_window_{parent.type.value}_{string_to_hash(str(parent))}"
|
|
266
272
|
return window_item_to_concept(parent, name, namespace, purpose, metadata)
|
|
267
273
|
elif isinstance(parent, FilterItem):
|
|
274
|
+
if not name:
|
|
275
|
+
name = f"_filter_{parent.content.name}_{string_to_hash(str(parent))}"
|
|
268
276
|
return filter_item_to_concept(parent, name, namespace, purpose, metadata)
|
|
269
277
|
elif isinstance(parent, Function):
|
|
278
|
+
if not name:
|
|
279
|
+
name = f"_func_{parent.operator.value}_{string_to_hash(str(parent))}"
|
|
270
280
|
return function_to_concept(parent, name, namespace)
|
|
271
281
|
elif isinstance(parent, ListWrapper):
|
|
282
|
+
if not name:
|
|
283
|
+
name = f"{VIRTUAL_CONCEPT_PREFIX}_{string_to_hash(str(parent))}"
|
|
272
284
|
return constant_to_concept(parent, name, namespace, purpose, metadata)
|
|
273
285
|
else:
|
|
286
|
+
if not name:
|
|
287
|
+
name = f"{VIRTUAL_CONCEPT_PREFIX}_{string_to_hash(str(parent))}"
|
|
274
288
|
return constant_to_concept(parent, name, namespace, purpose, metadata)
|
trilogy/parsing/parse_engine.py
CHANGED
|
@@ -16,7 +16,6 @@ from trilogy.core.internal import INTERNAL_NAMESPACE, ALL_ROWS_CONCEPT
|
|
|
16
16
|
from trilogy.constants import (
|
|
17
17
|
DEFAULT_NAMESPACE,
|
|
18
18
|
NULL_VALUE,
|
|
19
|
-
VIRTUAL_CONCEPT_PREFIX,
|
|
20
19
|
MagicConstants,
|
|
21
20
|
)
|
|
22
21
|
from trilogy.core.enums import (
|
|
@@ -109,7 +108,6 @@ from trilogy.core.models import (
|
|
|
109
108
|
HavingClause,
|
|
110
109
|
)
|
|
111
110
|
from trilogy.parsing.exceptions import ParseError
|
|
112
|
-
from trilogy.utility import string_to_hash
|
|
113
111
|
from trilogy.parsing.common import (
|
|
114
112
|
agg_wrapper_to_concept,
|
|
115
113
|
window_item_to_concept,
|
|
@@ -739,8 +737,8 @@ class ParseToObjects(Transformer):
|
|
|
739
737
|
x = arbitrary_to_concept(
|
|
740
738
|
x,
|
|
741
739
|
namespace=namespace,
|
|
742
|
-
name=f"{VIRTUAL_CONCEPT_PREFIX}_{string_to_hash(str(x))}",
|
|
743
740
|
)
|
|
741
|
+
self.environment.add_concept(x)
|
|
744
742
|
return x
|
|
745
743
|
|
|
746
744
|
return [
|
|
@@ -781,6 +779,11 @@ class ParseToObjects(Transformer):
|
|
|
781
779
|
def rawsql_statement(self, meta: Meta, args) -> RawSQLStatement:
|
|
782
780
|
return RawSQLStatement(meta=Metadata(line_number=meta.line), text=args[0])
|
|
783
781
|
|
|
782
|
+
def resolve_import_address(self, address) -> str:
|
|
783
|
+
with open(address, "r", encoding="utf-8") as f:
|
|
784
|
+
text = f.read()
|
|
785
|
+
return text
|
|
786
|
+
|
|
784
787
|
def import_statement(self, args: list[str]) -> ImportStatement:
|
|
785
788
|
alias = args[-1]
|
|
786
789
|
path = args[0].split(".")
|
|
@@ -790,8 +793,7 @@ class ParseToObjects(Transformer):
|
|
|
790
793
|
nparser = self.parsed[target]
|
|
791
794
|
else:
|
|
792
795
|
try:
|
|
793
|
-
|
|
794
|
-
text = f.read()
|
|
796
|
+
text = self.resolve_import_address(target)
|
|
795
797
|
nparser = ParseToObjects(
|
|
796
798
|
visit_tokens=True,
|
|
797
799
|
text=text,
|
|
@@ -1093,7 +1095,6 @@ class ParseToObjects(Transformer):
|
|
|
1093
1095
|
left = arbitrary_to_concept(
|
|
1094
1096
|
args[0],
|
|
1095
1097
|
namespace=self.environment.namespace,
|
|
1096
|
-
name=f"{VIRTUAL_CONCEPT_PREFIX}_{string_to_hash(str(args[0]))}",
|
|
1097
1098
|
)
|
|
1098
1099
|
self.environment.add_concept(left)
|
|
1099
1100
|
else:
|
|
@@ -1102,7 +1103,6 @@ class ParseToObjects(Transformer):
|
|
|
1102
1103
|
right = arbitrary_to_concept(
|
|
1103
1104
|
args[2],
|
|
1104
1105
|
namespace=self.environment.namespace,
|
|
1105
|
-
name=f"{VIRTUAL_CONCEPT_PREFIX}_{string_to_hash(str(args[2]))}",
|
|
1106
1106
|
)
|
|
1107
1107
|
self.environment.add_concept(right)
|
|
1108
1108
|
else:
|
|
@@ -1137,7 +1137,6 @@ class ParseToObjects(Transformer):
|
|
|
1137
1137
|
right = arbitrary_to_concept(
|
|
1138
1138
|
right,
|
|
1139
1139
|
namespace=self.environment.namespace,
|
|
1140
|
-
name=f"{VIRTUAL_CONCEPT_PREFIX}_{string_to_hash(str(right))}",
|
|
1141
1140
|
)
|
|
1142
1141
|
self.environment.add_concept(right, meta=meta)
|
|
1143
1142
|
return SubselectComparison(
|
|
@@ -1186,8 +1185,9 @@ class ParseToObjects(Transformer):
|
|
|
1186
1185
|
def window_item_order(self, args):
|
|
1187
1186
|
return WindowItemOrder(contents=args[0])
|
|
1188
1187
|
|
|
1189
|
-
|
|
1190
|
-
|
|
1188
|
+
@v_args(meta=True)
|
|
1189
|
+
def window_item(self, meta, args) -> WindowItem:
|
|
1190
|
+
type: WindowType = args[0]
|
|
1191
1191
|
order_by = []
|
|
1192
1192
|
over = []
|
|
1193
1193
|
index = None
|
|
@@ -1203,6 +1203,14 @@ class ParseToObjects(Transformer):
|
|
|
1203
1203
|
concept = self.environment.concepts[item]
|
|
1204
1204
|
elif isinstance(item, Concept):
|
|
1205
1205
|
concept = item
|
|
1206
|
+
elif isinstance(item, WindowType):
|
|
1207
|
+
type = item
|
|
1208
|
+
else:
|
|
1209
|
+
concept = arbitrary_to_concept(
|
|
1210
|
+
item,
|
|
1211
|
+
namespace=self.environment.namespace,
|
|
1212
|
+
)
|
|
1213
|
+
self.environment.add_concept(concept, meta=meta)
|
|
1206
1214
|
assert concept
|
|
1207
1215
|
return WindowItem(
|
|
1208
1216
|
type=type, content=concept, over=over, order_by=order_by, index=index
|
trilogy/parsing/trilogy.lark
CHANGED
|
@@ -90,9 +90,9 @@
|
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
// rank/lag/lead
|
|
93
|
-
WINDOW_TYPE: ("row_number"i|"rank"i|"lag"i|"lead"i | "sum"i) /[\s]+/
|
|
93
|
+
WINDOW_TYPE: ("row_number"i|"rank"i|"lag"i|"lead"i | "sum"i | "avg"i | "max"i | "min"i ) /[\s]+/
|
|
94
94
|
|
|
95
|
-
window_item: WINDOW_TYPE int_lit?
|
|
95
|
+
window_item: WINDOW_TYPE int_lit? expr window_item_over? window_item_order?
|
|
96
96
|
|
|
97
97
|
window_item_over: ("OVER"i over_list)
|
|
98
98
|
|
|
@@ -296,7 +296,7 @@
|
|
|
296
296
|
|
|
297
297
|
MODIFIER: "Optional"i | "Partial"i | "Nullable"i
|
|
298
298
|
|
|
299
|
-
SHORTHAND_MODIFIER: "~"
|
|
299
|
+
SHORTHAND_MODIFIER: "~" | "?"
|
|
300
300
|
|
|
301
301
|
struct_type: "struct"i "<" ((data_type | IDENTIFIER) ",")* (data_type | IDENTIFIER) ","? ">"
|
|
302
302
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|