pytrilogy 0.0.1.107__py3-none-any.whl → 0.0.1.108__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.1.107.dist-info → pytrilogy-0.0.1.108.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.1.107.dist-info → pytrilogy-0.0.1.108.dist-info}/RECORD +21 -22
- trilogy/__init__.py +1 -1
- trilogy/core/env_processor.py +2 -0
- trilogy/core/models.py +15 -6
- trilogy/core/optimization.py +1 -1
- trilogy/core/processing/concept_strategies_v3.py +11 -4
- trilogy/core/processing/node_generators/__init__.py +2 -2
- trilogy/core/processing/node_generators/{concept_merge.py → concept_merge_node.py} +57 -11
- trilogy/core/processing/nodes/__init__.py +10 -4
- trilogy/core/processing/nodes/base_node.py +2 -0
- trilogy/core/processing/nodes/merge_node.py +3 -0
- trilogy/core/query_processor.py +1 -0
- trilogy/dialect/base.py +4 -1
- trilogy/dialect/config.py +1 -16
- trilogy/dialect/presto.py +5 -3
- trilogy/docs/__init__.py +0 -0
- {pytrilogy-0.0.1.107.dist-info → pytrilogy-0.0.1.108.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.1.107.dist-info → pytrilogy-0.0.1.108.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.1.107.dist-info → pytrilogy-0.0.1.108.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.1.107.dist-info → pytrilogy-0.0.1.108.dist-info}/top_level.txt +0 -0
- /trilogy/core/processing/node_generators/{merge_node.py → node_merge_node.py} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
trilogy/__init__.py,sha256=
|
|
1
|
+
trilogy/__init__.py,sha256=XUWHIoJWL4bt0ApBe3huE8vvIiYJxxfm4krEBMhpLUc,292
|
|
2
2
|
trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
trilogy/constants.py,sha256=LxiK2TiVQPEa6tXkxWk9DJHOR3zsGNSqgQuqtOf66cw,518
|
|
4
4
|
trilogy/engine.py,sha256=R5ubIxYyrxRExz07aZCUfrTsoXCHQ8DKFTDsobXdWdA,1102
|
|
@@ -9,53 +9,52 @@ 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
11
|
trilogy/core/enums.py,sha256=KEZQTzJ8tlGIukuUwQUIG1FTHOP1B4i0EeCgFjfsbDw,5394
|
|
12
|
-
trilogy/core/env_processor.py,sha256=
|
|
12
|
+
trilogy/core/env_processor.py,sha256=SU-jpaGfoWLe9sGTeQYG1qjVnwGQ7TwctmnJRlfzluc,1459
|
|
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
16
|
trilogy/core/functions.py,sha256=zkRReytiotOBAW-a3Ri5eoejZDYTt2-7Op80ZxZxUmw,9129
|
|
17
17
|
trilogy/core/graph_models.py,sha256=oJUMSpmYhqXlavckHLpR07GJxuQ8dZ1VbB1fB0KaS8c,2036
|
|
18
18
|
trilogy/core/internal.py,sha256=jNGFHKENnbMiMCtAgsnLZYVSENDK4b5ALecXFZpTDzQ,1075
|
|
19
|
-
trilogy/core/models.py,sha256=
|
|
20
|
-
trilogy/core/optimization.py,sha256=
|
|
21
|
-
trilogy/core/query_processor.py,sha256=
|
|
19
|
+
trilogy/core/models.py,sha256=JvqqYCQorhHRa7haVRBYCGmmjYq4jWKjOgPqbf2eTNI,109050
|
|
20
|
+
trilogy/core/optimization.py,sha256=chfzpLVJo9eg8H4e2hdnpRqWMDTQ3tJWPdDfGESa-EU,4510
|
|
21
|
+
trilogy/core/query_processor.py,sha256=6BqLYPwyFkRtueTIRFZi3IcVFTpbpGRNowayhSn3_AY,11805
|
|
22
22
|
trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
-
trilogy/core/processing/concept_strategies_v3.py,sha256=
|
|
23
|
+
trilogy/core/processing/concept_strategies_v3.py,sha256=27lZXFLgDEF3sh2MUR7HX_atVz7TC1fJB7z3oxa1TcY,22610
|
|
24
24
|
trilogy/core/processing/graph_utils.py,sha256=ulCJ4hYAISbUxLD6VM2fah9RBPGIXSEHEPeRBSFl0Rs,1197
|
|
25
25
|
trilogy/core/processing/utility.py,sha256=Gk35HgyIG2SSUyI5OHZcB0bw1PZUVC_aNc9Sre6xPQU,10535
|
|
26
|
-
trilogy/core/processing/node_generators/__init__.py,sha256=
|
|
26
|
+
trilogy/core/processing/node_generators/__init__.py,sha256=LIs6uBEum8LDc-26zjyAwjxa-ay2ok9tKtPjDNvbVkE,757
|
|
27
27
|
trilogy/core/processing/node_generators/basic_node.py,sha256=tVPmg0r0kDdABkmn6z4sxsk1hKy9yTT_Xvl1eVN2Zck,2162
|
|
28
28
|
trilogy/core/processing/node_generators/common.py,sha256=RWaynnlCeF8bhGXVLYWpUFrdngMF95TJwCFue73bXZo,8881
|
|
29
|
-
trilogy/core/processing/node_generators/
|
|
29
|
+
trilogy/core/processing/node_generators/concept_merge_node.py,sha256=TRbOIjLWfLB0Nl6YmMV1ao0qhPP6OQDd9M3UViWkCBU,6621
|
|
30
30
|
trilogy/core/processing/node_generators/filter_node.py,sha256=CGALiTzKhPAvXPFAguIQfjf6I3pjlafY0uaaM9MTkIE,3414
|
|
31
31
|
trilogy/core/processing/node_generators/group_node.py,sha256=xWI1xNIXEOj6jlRGD9hcv2_vVNvY6lpzJl6pQ8HuFBE,2988
|
|
32
32
|
trilogy/core/processing/node_generators/group_to_node.py,sha256=BzPdYwzoo8gRMH7BDffTTXq4z-mjfCEzvfB5I-P0_nw,2941
|
|
33
|
-
trilogy/core/processing/node_generators/merge_node.py,sha256=sQQ9jhw1oAJh649DBAJX6U7r_E_piFS95mxKvm7pxqQ,5818
|
|
34
33
|
trilogy/core/processing/node_generators/multiselect_node.py,sha256=vP84dnLQy6dtypi6mUbt9sMAcmmrTgQ1Oz4GI6X1IEo,6421
|
|
34
|
+
trilogy/core/processing/node_generators/node_merge_node.py,sha256=sQQ9jhw1oAJh649DBAJX6U7r_E_piFS95mxKvm7pxqQ,5818
|
|
35
35
|
trilogy/core/processing/node_generators/rowset_node.py,sha256=K-aoLi0OSfNADXR5_vxDcNv2dJeFy30XNp_IaaWWJ6o,4684
|
|
36
36
|
trilogy/core/processing/node_generators/select_node.py,sha256=xeCqIUEubrf3u_QQfbGdf1BG4fO0HYQ64hiFur8NUqY,20080
|
|
37
37
|
trilogy/core/processing/node_generators/unnest_node.py,sha256=s1VXQZSf1LnX3ISeQ5JzmzmCKUw30-5OK_f0YTB9_48,1031
|
|
38
38
|
trilogy/core/processing/node_generators/window_node.py,sha256=ekazi5eXxnShpcp-qukXNG4DHFdULoXrX-YWUWLNEpM,2527
|
|
39
|
-
trilogy/core/processing/nodes/__init__.py,sha256=
|
|
40
|
-
trilogy/core/processing/nodes/base_node.py,sha256=
|
|
39
|
+
trilogy/core/processing/nodes/__init__.py,sha256=gzKxGSduIQ5QwpMWrmwSYiE8sg2mWejwVn0VvjYc6s0,3879
|
|
40
|
+
trilogy/core/processing/nodes/base_node.py,sha256=075SP0fAqCZGxxE9Rn5-Qfd2mWd9J7NannGFkNfTkuY,9068
|
|
41
41
|
trilogy/core/processing/nodes/filter_node.py,sha256=DqSRv8voEajPZqzeeiIsxuv4ubvsmeQcCW6x_v2CmOk,1359
|
|
42
42
|
trilogy/core/processing/nodes/group_node.py,sha256=Y_NWB_AwFrE-YithjZ7lYYDN4e0el4su3ICq2EIr3HA,3837
|
|
43
|
-
trilogy/core/processing/nodes/merge_node.py,sha256=
|
|
43
|
+
trilogy/core/processing/nodes/merge_node.py,sha256=a9JkWNSb5ikRnr33DJrlIpZ_OXVK7XDF4FFnqNab4wI,12758
|
|
44
44
|
trilogy/core/processing/nodes/select_node_v2.py,sha256=tAADeVruch-flFiedbY1zi7ukMG2RpWecvxxZ5aL3ZU,6354
|
|
45
45
|
trilogy/core/processing/nodes/unnest_node.py,sha256=t4kY3a_dR3iXistPemStfdw0uJfnxwTcoQg1HiDa3xo,1501
|
|
46
46
|
trilogy/core/processing/nodes/window_node.py,sha256=QjAWgqBZqFSRCPwc7JBmgQJobWW50rsHI0pjJe0Zzg0,926
|
|
47
47
|
trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
48
|
-
trilogy/dialect/base.py,sha256=
|
|
48
|
+
trilogy/dialect/base.py,sha256=xPB5mh6471VJLHxNdXrYvi7q7vJC_tioVR1LrLcoZc0,29394
|
|
49
49
|
trilogy/dialect/bigquery.py,sha256=9vxQn2BMv_oTGQSWQpoN5ho_OgqMWaHH9e-5vQVf44c,2906
|
|
50
50
|
trilogy/dialect/common.py,sha256=zWrYmvevlXznocw9uGHmY5Ws1rp_kICm9zA_ulTe4eg,2165
|
|
51
|
-
trilogy/dialect/config.py,sha256=
|
|
51
|
+
trilogy/dialect/config.py,sha256=tLVEMctaTDhUgARKXUNfHUcIolGaALkQ0RavUvXAY4w,2994
|
|
52
52
|
trilogy/dialect/duckdb.py,sha256=Ddyt68sr8IL2HnZMenyytoD65FXwY_O2pz1McyS0bis,3075
|
|
53
53
|
trilogy/dialect/enums.py,sha256=4NdpsydBpDn6jnh0JzFz5VvQEtnShErWtWHVyT6TNpw,3948
|
|
54
54
|
trilogy/dialect/postgres.py,sha256=r47xbCA7nfEYENofiVfLZ-SnReNfDmUmW4OSHVkkP4E,3206
|
|
55
|
-
trilogy/dialect/presto.py,sha256=
|
|
55
|
+
trilogy/dialect/presto.py,sha256=8zjRn8AeYXZQGuUi-afyBWLet8o-LSt6gm5IH7bTdiw,2987
|
|
56
56
|
trilogy/dialect/snowflake.py,sha256=N3HknYgN-fjD7BLX1Ucj-ss_ku2Ox8DgLsF3BIHutHo,2941
|
|
57
57
|
trilogy/dialect/sql_server.py,sha256=UrLeA9bxiFJ4qpGsqVJqBybQCyJhetMebe8IzQW1q9s,2900
|
|
58
|
-
trilogy/docs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
59
58
|
trilogy/hooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
59
|
trilogy/hooks/base_hook.py,sha256=Xkb-A2qCHozYjum0A36zOy5PwTVwrP3NLDF0U2GpgHo,1100
|
|
61
60
|
trilogy/hooks/graph_hook.py,sha256=i-Tv9sxZU0sMc-God8bLLz-nAg4-wYafogZtHaU8LXw,801
|
|
@@ -70,9 +69,9 @@ trilogy/parsing/parse_engine.py,sha256=TLy56pDatDfzfwbJkrJ-XXB05s_VW9_iRrkwtKR0G
|
|
|
70
69
|
trilogy/parsing/render.py,sha256=fxjpq2FZLgllw_d4cru-t_IXNPAz2DmYkT7v9ED0XRI,11540
|
|
71
70
|
trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
72
71
|
trilogy/scripts/trilogy.py,sha256=PHxvv6f2ODv0esyyhWxlARgra8dVhqQhYl0lTrSyVNo,3729
|
|
73
|
-
pytrilogy-0.0.1.
|
|
74
|
-
pytrilogy-0.0.1.
|
|
75
|
-
pytrilogy-0.0.1.
|
|
76
|
-
pytrilogy-0.0.1.
|
|
77
|
-
pytrilogy-0.0.1.
|
|
78
|
-
pytrilogy-0.0.1.
|
|
72
|
+
pytrilogy-0.0.1.108.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
73
|
+
pytrilogy-0.0.1.108.dist-info/METADATA,sha256=HUP7pQQZBmSTKuUUioE53ys3fCS76Xjwj2CF5SykMOs,7882
|
|
74
|
+
pytrilogy-0.0.1.108.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
|
|
75
|
+
pytrilogy-0.0.1.108.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
|
|
76
|
+
pytrilogy-0.0.1.108.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
77
|
+
pytrilogy-0.0.1.108.dist-info/RECORD,,
|
trilogy/__init__.py
CHANGED
trilogy/core/env_processor.py
CHANGED
|
@@ -21,6 +21,8 @@ def generate_graph(
|
|
|
21
21
|
for source in concept.sources:
|
|
22
22
|
generic = source.with_default_grain()
|
|
23
23
|
g.add_edge(generic, node_name)
|
|
24
|
+
|
|
25
|
+
# link the concept to the generic source
|
|
24
26
|
if concept.derivation == PurposeLineage.MERGE:
|
|
25
27
|
g.add_edge(node_name, generic)
|
|
26
28
|
for _, dataset in environment.datasources.items():
|
trilogy/core/models.py
CHANGED
|
@@ -1512,7 +1512,7 @@ class MergeStatement(Namespaced, BaseModel):
|
|
|
1512
1512
|
if z.address == x.address:
|
|
1513
1513
|
return z
|
|
1514
1514
|
raise SyntaxError(
|
|
1515
|
-
f"Could not find upstream map for multiselect {str(concept)} on cte ({cte})"
|
|
1515
|
+
f"Could not find upstream map for multiselect {str(concept)} on cte ({cte.name})"
|
|
1516
1516
|
)
|
|
1517
1517
|
|
|
1518
1518
|
def with_namespace(self, namespace: str) -> "MergeStatement":
|
|
@@ -1779,6 +1779,7 @@ class QueryDatasource(BaseModel):
|
|
|
1779
1779
|
source_type: SourceType = SourceType.SELECT
|
|
1780
1780
|
partial_concepts: List[Concept] = Field(default_factory=list)
|
|
1781
1781
|
join_derived_concepts: List[Concept] = Field(default_factory=list)
|
|
1782
|
+
hidden_concepts: List[Concept] = Field(default_factory=list)
|
|
1782
1783
|
force_group: bool | None = None
|
|
1783
1784
|
|
|
1784
1785
|
@property
|
|
@@ -1997,6 +1998,7 @@ class CTE(BaseModel):
|
|
|
1997
1998
|
condition: Optional[Union["Conditional", "Comparison", "Parenthetical"]] = None
|
|
1998
1999
|
partial_concepts: List[Concept] = Field(default_factory=list)
|
|
1999
2000
|
join_derived_concepts: List[Concept] = Field(default_factory=list)
|
|
2001
|
+
hidden_concepts: List[Concept] = Field(default_factory=list)
|
|
2000
2002
|
order_by: Optional[OrderBy] = None
|
|
2001
2003
|
limit: Optional[int] = None
|
|
2002
2004
|
requires_nesting: bool = True
|
|
@@ -2433,6 +2435,7 @@ class Environment(BaseModel):
|
|
|
2433
2435
|
cte_name_map: Dict[str, str] = Field(default_factory=dict)
|
|
2434
2436
|
|
|
2435
2437
|
materialized_concepts: List[Concept] = Field(default_factory=list)
|
|
2438
|
+
merged_concepts: Dict[str, Concept] = Field(default_factory=dict)
|
|
2436
2439
|
_parse_count: int = 0
|
|
2437
2440
|
|
|
2438
2441
|
@classmethod
|
|
@@ -2460,7 +2463,7 @@ class Environment(BaseModel):
|
|
|
2460
2463
|
f.write(self.model_dump_json())
|
|
2461
2464
|
return ppath
|
|
2462
2465
|
|
|
2463
|
-
def
|
|
2466
|
+
def gen_concept_list_caches(self) -> None:
|
|
2464
2467
|
concrete_addresses = set()
|
|
2465
2468
|
for datasource in self.datasources.values():
|
|
2466
2469
|
for concept in datasource.output_concepts:
|
|
@@ -2468,6 +2471,12 @@ class Environment(BaseModel):
|
|
|
2468
2471
|
self.materialized_concepts = [
|
|
2469
2472
|
c for c in self.concepts.values() if c.address in concrete_addresses
|
|
2470
2473
|
]
|
|
2474
|
+
for concept in self.concepts.values():
|
|
2475
|
+
if concept.derivation == PurposeLineage.MERGE:
|
|
2476
|
+
ms = concept.lineage
|
|
2477
|
+
assert isinstance(ms, MergeStatement)
|
|
2478
|
+
for parent in ms.concepts:
|
|
2479
|
+
self.merged_concepts[parent.address] = concept
|
|
2471
2480
|
|
|
2472
2481
|
def validate_concept(self, lookup: str, meta: Meta | None = None):
|
|
2473
2482
|
existing: Concept = self.concepts.get(lookup) # type: ignore
|
|
@@ -2503,7 +2512,7 @@ class Environment(BaseModel):
|
|
|
2503
2512
|
self.concepts[f"{alias}.{key}"] = concept.with_namespace(alias)
|
|
2504
2513
|
for key, datasource in environment.datasources.items():
|
|
2505
2514
|
self.datasources[f"{alias}.{key}"] = datasource.with_namespace(alias)
|
|
2506
|
-
self.
|
|
2515
|
+
self.gen_concept_list_caches()
|
|
2507
2516
|
return self
|
|
2508
2517
|
|
|
2509
2518
|
def add_file_import(self, path: str, alias: str, env: Environment | None = None):
|
|
@@ -2602,7 +2611,7 @@ class Environment(BaseModel):
|
|
|
2602
2611
|
from trilogy.core.environment_helpers import generate_related_concepts
|
|
2603
2612
|
|
|
2604
2613
|
generate_related_concepts(concept, self)
|
|
2605
|
-
self.
|
|
2614
|
+
self.gen_concept_list_caches()
|
|
2606
2615
|
return concept
|
|
2607
2616
|
|
|
2608
2617
|
def add_datasource(
|
|
@@ -2612,12 +2621,12 @@ class Environment(BaseModel):
|
|
|
2612
2621
|
):
|
|
2613
2622
|
if not datasource.namespace or datasource.namespace == DEFAULT_NAMESPACE:
|
|
2614
2623
|
self.datasources[datasource.name] = datasource
|
|
2615
|
-
self.
|
|
2624
|
+
self.gen_concept_list_caches()
|
|
2616
2625
|
return datasource
|
|
2617
2626
|
self.datasources[datasource.namespace + "." + datasource.identifier] = (
|
|
2618
2627
|
datasource
|
|
2619
2628
|
)
|
|
2620
|
-
self.
|
|
2629
|
+
self.gen_concept_list_caches()
|
|
2621
2630
|
return datasource
|
|
2622
2631
|
|
|
2623
2632
|
|
trilogy/core/optimization.py
CHANGED
|
@@ -136,6 +136,6 @@ def optimize_ctes(
|
|
|
136
136
|
select.where_clause.conditional if select.where_clause else None
|
|
137
137
|
)
|
|
138
138
|
root_cte.requires_nesting = False
|
|
139
|
-
sort_select_output(
|
|
139
|
+
sort_select_output(root_cte, select)
|
|
140
140
|
|
|
141
141
|
return filter_irrelevant_ctes(input, root_cte)
|
|
@@ -337,7 +337,9 @@ def generate_node(
|
|
|
337
337
|
|
|
338
338
|
|
|
339
339
|
def validate_stack(
|
|
340
|
-
stack: List[StrategyNode],
|
|
340
|
+
stack: List[StrategyNode],
|
|
341
|
+
concepts: List[Concept],
|
|
342
|
+
accept_partial: bool = False,
|
|
341
343
|
) -> tuple[ValidationResult, set[str], set[str], set[str]]:
|
|
342
344
|
found_map = defaultdict(set)
|
|
343
345
|
found_addresses: set[str] = set()
|
|
@@ -357,6 +359,7 @@ def validate_stack(
|
|
|
357
359
|
if accept_partial:
|
|
358
360
|
found_addresses.add(concept.address)
|
|
359
361
|
found_map[str(node)].add(concept)
|
|
362
|
+
# zip in those we know we found
|
|
360
363
|
if not all([c.address in found_addresses for c in concepts]):
|
|
361
364
|
return (
|
|
362
365
|
ValidationResult.INCOMPLETE,
|
|
@@ -388,7 +391,7 @@ def search_concepts(
|
|
|
388
391
|
hist = history.get_history(mandatory_list, accept_partial)
|
|
389
392
|
if hist is not False:
|
|
390
393
|
logger.info(
|
|
391
|
-
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Returning search node from history"
|
|
394
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Returning search node from history for {[c.address for c in mandatory_list]} with accept_partial {accept_partial}"
|
|
392
395
|
)
|
|
393
396
|
assert not isinstance(hist, bool)
|
|
394
397
|
return hist
|
|
@@ -454,7 +457,6 @@ def _search_concepts(
|
|
|
454
457
|
if node:
|
|
455
458
|
stack.append(node)
|
|
456
459
|
node.resolve()
|
|
457
|
-
found.add(priority_concept.address)
|
|
458
460
|
# these concepts should not be attempted to be sourced again
|
|
459
461
|
# as fetching them requires operating on a subset of concepts
|
|
460
462
|
if priority_concept.derivation in [
|
|
@@ -473,11 +475,12 @@ def _search_concepts(
|
|
|
473
475
|
complete, found, missing, partial = validate_stack(
|
|
474
476
|
stack, mandatory_list, accept_partial
|
|
475
477
|
)
|
|
476
|
-
|
|
478
|
+
|
|
477
479
|
logger.info(
|
|
478
480
|
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} finished concept loop for {priority_concept} flag for accepting partial addresses is "
|
|
479
481
|
f" {accept_partial} (complete: {complete}), have {found} from {[n for n in stack]} (missing {missing} partial {partial}), attempted {attempted}"
|
|
480
482
|
)
|
|
483
|
+
# early exit if we have a complete stack with one node
|
|
481
484
|
# we can only early exit if we have a complete stack
|
|
482
485
|
# and we are not looking for more non-partial sources
|
|
483
486
|
if complete == ValidationResult.COMPLETE and (
|
|
@@ -514,6 +517,10 @@ def _search_concepts(
|
|
|
514
517
|
parents=stack,
|
|
515
518
|
depth=depth,
|
|
516
519
|
partial_concepts=all_partial,
|
|
520
|
+
# always hide merge concepts
|
|
521
|
+
hidden_concepts=[
|
|
522
|
+
x for x in mandatory_list if x.derivation == PurposeLineage.MERGE
|
|
523
|
+
],
|
|
517
524
|
)
|
|
518
525
|
|
|
519
526
|
# ensure we can resolve our final merge
|
|
@@ -5,10 +5,10 @@ from .group_to_node import gen_group_to_node
|
|
|
5
5
|
from .basic_node import gen_basic_node
|
|
6
6
|
from .select_node import gen_select_node
|
|
7
7
|
from .unnest_node import gen_unnest_node
|
|
8
|
-
from .
|
|
8
|
+
from .node_merge_node import gen_merge_node
|
|
9
9
|
from .rowset_node import gen_rowset_node
|
|
10
10
|
from .multiselect_node import gen_multiselect_node
|
|
11
|
-
from .
|
|
11
|
+
from .concept_merge_node import gen_concept_merge_node
|
|
12
12
|
|
|
13
13
|
__all__ = [
|
|
14
14
|
"gen_filter_node",
|
|
@@ -7,27 +7,30 @@ from trilogy.core.processing.nodes import MergeNode, NodeJoin, History
|
|
|
7
7
|
from trilogy.core.processing.nodes.base_node import concept_list_to_grain, StrategyNode
|
|
8
8
|
from typing import List
|
|
9
9
|
|
|
10
|
-
from trilogy.core.enums import JoinType
|
|
10
|
+
from trilogy.core.enums import JoinType, PurposeLineage
|
|
11
11
|
from trilogy.constants import logger
|
|
12
12
|
from trilogy.core.processing.utility import padding
|
|
13
13
|
from trilogy.core.processing.node_generators.common import concept_to_relevant_joins
|
|
14
14
|
from itertools import combinations
|
|
15
15
|
from trilogy.core.processing.node_generators.common import resolve_join_order
|
|
16
|
+
from trilogy.utility import unique
|
|
17
|
+
|
|
16
18
|
|
|
17
19
|
LOGGER_PREFIX = "[GEN_CONCEPT_MERGE_NODE]"
|
|
18
20
|
|
|
19
21
|
|
|
20
|
-
def merge_joins(
|
|
22
|
+
def merge_joins(
|
|
23
|
+
parents: List[StrategyNode], merge_concepts: List[Concept]
|
|
24
|
+
) -> List[NodeJoin]:
|
|
21
25
|
output = []
|
|
22
26
|
for left, right in combinations(parents, 2):
|
|
23
27
|
output.append(
|
|
24
28
|
NodeJoin(
|
|
25
29
|
left_node=left,
|
|
26
30
|
right_node=right,
|
|
27
|
-
concepts=
|
|
28
|
-
base.merge_concept,
|
|
29
|
-
],
|
|
31
|
+
concepts=merge_concepts,
|
|
30
32
|
join_type=JoinType.FULL,
|
|
33
|
+
filter_to_mutual=True,
|
|
31
34
|
)
|
|
32
35
|
)
|
|
33
36
|
return resolve_join_order(output)
|
|
@@ -50,37 +53,70 @@ def gen_concept_merge_node(
|
|
|
50
53
|
lineage: MergeStatement = concept.lineage
|
|
51
54
|
|
|
52
55
|
base_parents: List[StrategyNode] = []
|
|
56
|
+
|
|
57
|
+
# get additional concepts that should be merged across the environments
|
|
58
|
+
additional_merge: List[Concept] = [*lineage.concepts]
|
|
59
|
+
for x in local_optional:
|
|
60
|
+
if x.address in environment.merged_concepts:
|
|
61
|
+
ms = environment.merged_concepts[x.address].lineage
|
|
62
|
+
assert isinstance(ms, MergeStatement)
|
|
63
|
+
additional_merge += ms.concepts
|
|
64
|
+
|
|
53
65
|
for select in lineage.concepts:
|
|
54
66
|
# if it's a merge concept, filter it out of the optional
|
|
55
67
|
sub_optional = [
|
|
56
68
|
x
|
|
57
69
|
for x in local_optional
|
|
58
|
-
if x.address not in
|
|
70
|
+
if x.address not in environment.merged_concepts
|
|
71
|
+
and x.namespace == select.namespace
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
sub_additional_merge = [
|
|
75
|
+
x for x in additional_merge if x.namespace == select.namespace
|
|
59
76
|
]
|
|
77
|
+
sub_optional += sub_additional_merge
|
|
78
|
+
final: List[Concept] = unique([select] + sub_optional, "address")
|
|
60
79
|
snode: StrategyNode = source_concepts(
|
|
61
|
-
mandatory_list=
|
|
80
|
+
mandatory_list=final,
|
|
62
81
|
environment=environment,
|
|
63
82
|
g=g,
|
|
64
83
|
depth=depth + 1,
|
|
65
84
|
history=history,
|
|
66
85
|
)
|
|
86
|
+
|
|
67
87
|
if not snode:
|
|
68
88
|
logger.info(
|
|
69
89
|
f"{padding(depth)}{LOGGER_PREFIX} Cannot generate merge node for {concept}"
|
|
70
90
|
)
|
|
71
91
|
return None
|
|
72
|
-
|
|
92
|
+
for x in sub_additional_merge:
|
|
93
|
+
snode.add_output_concept(environment.merged_concepts[x.address])
|
|
73
94
|
base_parents.append(snode)
|
|
74
95
|
|
|
75
|
-
node_joins = merge_joins(
|
|
96
|
+
node_joins = merge_joins(
|
|
97
|
+
base_parents,
|
|
98
|
+
unique(
|
|
99
|
+
[environment.merged_concepts[x.address] for x in additional_merge],
|
|
100
|
+
"address",
|
|
101
|
+
),
|
|
102
|
+
)
|
|
76
103
|
|
|
77
104
|
enrichment = set([x.address for x in local_optional])
|
|
78
|
-
outputs = [
|
|
105
|
+
outputs = [
|
|
106
|
+
x
|
|
107
|
+
for y in base_parents
|
|
108
|
+
for x in y.output_concepts
|
|
109
|
+
if x.derivation != PurposeLineage.MERGE
|
|
110
|
+
]
|
|
79
111
|
|
|
80
112
|
additional_relevant = [x for x in outputs if x.address in enrichment]
|
|
113
|
+
final_outputs = outputs + additional_relevant + [concept]
|
|
81
114
|
node = MergeNode(
|
|
82
115
|
input_concepts=[x for y in base_parents for x in y.output_concepts],
|
|
83
|
-
output_concepts=
|
|
116
|
+
output_concepts=[x for x in final_outputs],
|
|
117
|
+
hidden_concepts=[
|
|
118
|
+
x for x in final_outputs if x.derivation == PurposeLineage.MERGE
|
|
119
|
+
],
|
|
84
120
|
environment=environment,
|
|
85
121
|
g=g,
|
|
86
122
|
depth=depth,
|
|
@@ -127,9 +163,19 @@ def gen_concept_merge_node(
|
|
|
127
163
|
)
|
|
128
164
|
return node
|
|
129
165
|
|
|
166
|
+
# we still need the hidden concepts to be returned to the search
|
|
167
|
+
# since they must be on the final node
|
|
168
|
+
# to avoid further recursion
|
|
169
|
+
# TODO: let the downstream search know they were found
|
|
130
170
|
return MergeNode(
|
|
131
171
|
input_concepts=enrich_node.output_concepts + node.output_concepts,
|
|
172
|
+
# also filter out the
|
|
132
173
|
output_concepts=node.output_concepts + local_optional,
|
|
174
|
+
hidden_concepts=[
|
|
175
|
+
x
|
|
176
|
+
for x in node.output_concepts + local_optional
|
|
177
|
+
if x.derivation == PurposeLineage.MERGE
|
|
178
|
+
],
|
|
133
179
|
environment=environment,
|
|
134
180
|
g=g,
|
|
135
181
|
depth=depth,
|
|
@@ -27,12 +27,18 @@ class History(BaseModel):
|
|
|
27
27
|
self,
|
|
28
28
|
search: list[Concept],
|
|
29
29
|
accept_partial: bool = False,
|
|
30
|
+
parent_key: str = "",
|
|
30
31
|
) -> StrategyNode | None | bool:
|
|
32
|
+
key = self._concepts_to_lookup(
|
|
33
|
+
search,
|
|
34
|
+
accept_partial,
|
|
35
|
+
)
|
|
36
|
+
if parent_key and parent_key == key:
|
|
37
|
+
raise ValueError(
|
|
38
|
+
f"Parent key {parent_key} is the same as the current key {key}"
|
|
39
|
+
)
|
|
31
40
|
return self.history.get(
|
|
32
|
-
|
|
33
|
-
search,
|
|
34
|
-
accept_partial,
|
|
35
|
-
),
|
|
41
|
+
key,
|
|
36
42
|
False,
|
|
37
43
|
)
|
|
38
44
|
|
|
@@ -107,6 +107,7 @@ class StrategyNode:
|
|
|
107
107
|
conditions: Conditional | Comparison | Parenthetical | None = None,
|
|
108
108
|
force_group: bool | None = None,
|
|
109
109
|
grain: Optional[Grain] = None,
|
|
110
|
+
hidden_concepts: List[Concept] | None = None,
|
|
110
111
|
):
|
|
111
112
|
self.input_concepts: List[Concept] = (
|
|
112
113
|
unique(input_concepts, "address") if input_concepts else []
|
|
@@ -129,6 +130,7 @@ class StrategyNode:
|
|
|
129
130
|
self.grain = grain
|
|
130
131
|
self.force_group = force_group
|
|
131
132
|
self.tainted = False
|
|
133
|
+
self.hidden_concepts = hidden_concepts or []
|
|
132
134
|
for parent in self.parents:
|
|
133
135
|
if not parent:
|
|
134
136
|
raise SyntaxError("Unresolvable parent")
|
|
@@ -104,6 +104,7 @@ class MergeNode(StrategyNode):
|
|
|
104
104
|
depth: int = 0,
|
|
105
105
|
grain: Grain | None = None,
|
|
106
106
|
conditions: Conditional | None = None,
|
|
107
|
+
hidden_concepts: List[Concept] | None = None,
|
|
107
108
|
):
|
|
108
109
|
super().__init__(
|
|
109
110
|
input_concepts=input_concepts,
|
|
@@ -117,6 +118,7 @@ class MergeNode(StrategyNode):
|
|
|
117
118
|
force_group=force_group,
|
|
118
119
|
grain=grain,
|
|
119
120
|
conditions=conditions,
|
|
121
|
+
hidden_concepts=hidden_concepts,
|
|
120
122
|
)
|
|
121
123
|
self.join_concepts = join_concepts
|
|
122
124
|
self.force_join_type = force_join_type
|
|
@@ -327,5 +329,6 @@ class MergeNode(StrategyNode):
|
|
|
327
329
|
partial_concepts=self.partial_concepts,
|
|
328
330
|
force_group=force_group,
|
|
329
331
|
condition=self.conditions,
|
|
332
|
+
hidden_concepts=self.hidden_concepts,
|
|
330
333
|
)
|
|
331
334
|
return qds
|
trilogy/core/query_processor.py
CHANGED
|
@@ -216,6 +216,7 @@ def datasource_to_ctes(
|
|
|
216
216
|
condition=query_datasource.condition,
|
|
217
217
|
partial_concepts=query_datasource.partial_concepts,
|
|
218
218
|
join_derived_concepts=query_datasource.join_derived_concepts,
|
|
219
|
+
hidden_concepts=query_datasource.hidden_concepts,
|
|
219
220
|
)
|
|
220
221
|
if cte.grain != query_datasource.grain:
|
|
221
222
|
raise ValueError("Grain was corrupted in CTE generation")
|
trilogy/dialect/base.py
CHANGED
|
@@ -437,6 +437,7 @@ class BaseDialect:
|
|
|
437
437
|
self.render_concept_sql(c, cte)
|
|
438
438
|
for c in cte.output_columns
|
|
439
439
|
if c.address not in [y.address for y in cte.join_derived_concepts]
|
|
440
|
+
and c.address not in [y.address for y in cte.hidden_concepts]
|
|
440
441
|
] + [
|
|
441
442
|
f"{self.QUOTE_CHARACTER}{c.safe_address}{self.QUOTE_CHARACTER}"
|
|
442
443
|
for c in cte.join_derived_concepts
|
|
@@ -444,7 +445,9 @@ class BaseDialect:
|
|
|
444
445
|
else:
|
|
445
446
|
# otherwse, assume we are unnesting directly in the select
|
|
446
447
|
select_columns = [
|
|
447
|
-
self.render_concept_sql(c, cte)
|
|
448
|
+
self.render_concept_sql(c, cte)
|
|
449
|
+
for c in cte.output_columns
|
|
450
|
+
if c.address not in [y.address for y in cte.hidden_concepts]
|
|
448
451
|
]
|
|
449
452
|
return CompiledCTE(
|
|
450
453
|
name=cte.name,
|
trilogy/dialect/config.py
CHANGED
|
@@ -100,22 +100,7 @@ class PrestoConfig(DialectConfig):
|
|
|
100
100
|
return f"presto://{self.username}:{self.password}@{self.host}:{self.port}/{self.catalog}"
|
|
101
101
|
|
|
102
102
|
|
|
103
|
-
class TrinoConfig(
|
|
104
|
-
def __init__(
|
|
105
|
-
self,
|
|
106
|
-
host: str,
|
|
107
|
-
port: int,
|
|
108
|
-
username: str,
|
|
109
|
-
password: str,
|
|
110
|
-
catalog: str,
|
|
111
|
-
schema: str | None = None,
|
|
112
|
-
):
|
|
113
|
-
self.host = host
|
|
114
|
-
self.port = port
|
|
115
|
-
self.username = username
|
|
116
|
-
self.password = password
|
|
117
|
-
self.catalog = catalog
|
|
118
|
-
self.schema = schema
|
|
103
|
+
class TrinoConfig(PrestoConfig):
|
|
119
104
|
|
|
120
105
|
def connection_string(self) -> str:
|
|
121
106
|
if self.schema:
|
trilogy/dialect/presto.py
CHANGED
|
@@ -27,6 +27,9 @@ FUNCTION_MAP = {
|
|
|
27
27
|
FunctionType.QUARTER: lambda x: f"EXTRACT(QUARTER from {x[0]})",
|
|
28
28
|
# math
|
|
29
29
|
FunctionType.DIVIDE: lambda x: f"SAFE_DIVIDE({x[0]},{x[1]})",
|
|
30
|
+
FunctionType.DATE_ADD: lambda x: f"DATE_ADD('{x[1]}', {x[2]}, {x[0]})",
|
|
31
|
+
FunctionType.CURRENT_DATE: lambda x: "CURRENT_DATE",
|
|
32
|
+
FunctionType.CURRENT_DATETIME: lambda x: "CURRENT_TIMESTAMP",
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
FUNCTION_GRAIN_MATCH_MAP = {
|
|
@@ -42,11 +45,10 @@ CREATE OR REPLACE TABLE {{ output.address }} AS
|
|
|
42
45
|
{% endif %}{%- if ctes %}
|
|
43
46
|
WITH {% for cte in ctes %}
|
|
44
47
|
{{cte.name}} as ({{cte.statement}}){% if not loop.last %},{% endif %}{% endfor %}{% endif %}
|
|
45
|
-
|
|
46
|
-
SELECT
|
|
47
48
|
{%- if full_select -%}
|
|
48
49
|
{{full_select}}
|
|
49
|
-
{%- else
|
|
50
|
+
{%- else %}
|
|
51
|
+
SELECT
|
|
50
52
|
{%- for select in select_columns %}
|
|
51
53
|
{{ select }}{% if not loop.last %},{% endif %}{% endfor %}
|
|
52
54
|
{% if base %}FROM
|
trilogy/docs/__init__.py
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|