pytrilogy 0.0.3.103__py3-none-any.whl → 0.0.3.105__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.3.103.dist-info → pytrilogy-0.0.3.105.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.3.103.dist-info → pytrilogy-0.0.3.105.dist-info}/RECORD +28 -27
- trilogy/__init__.py +1 -1
- trilogy/constants.py +1 -1
- trilogy/core/models/execute.py +1 -6
- trilogy/core/optimizations/predicate_pushdown.py +9 -1
- trilogy/core/processing/concept_strategies_v3.py +35 -14
- trilogy/core/processing/discovery_node_factory.py +6 -1
- trilogy/core/processing/discovery_utility.py +185 -14
- trilogy/core/processing/node_generators/basic_node.py +1 -0
- trilogy/core/processing/node_generators/common.py +1 -0
- trilogy/core/processing/node_generators/filter_node.py +4 -15
- trilogy/core/processing/node_generators/group_node.py +36 -0
- trilogy/core/processing/node_generators/multiselect_node.py +1 -1
- trilogy/core/processing/node_generators/node_merge_node.py +2 -6
- trilogy/core/processing/node_generators/rowset_node.py +1 -1
- trilogy/core/processing/node_generators/unnest_node.py +0 -5
- trilogy/core/processing/nodes/base_node.py +13 -2
- trilogy/core/processing/nodes/group_node.py +9 -91
- trilogy/core/processing/nodes/merge_node.py +9 -0
- trilogy/core/processing/utility.py +8 -0
- trilogy/dialect/base.py +3 -0
- trilogy/std/color.preql +3 -0
- trilogy/std/display.preql +3 -3
- {pytrilogy-0.0.3.103.dist-info → pytrilogy-0.0.3.105.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.3.103.dist-info → pytrilogy-0.0.3.105.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.3.103.dist-info → pytrilogy-0.0.3.105.dist-info}/licenses/LICENSE.md +0 -0
- {pytrilogy-0.0.3.103.dist-info → pytrilogy-0.0.3.105.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
pytrilogy-0.0.3.
|
|
2
|
-
trilogy/__init__.py,sha256=
|
|
3
|
-
trilogy/constants.py,sha256=
|
|
1
|
+
pytrilogy-0.0.3.105.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
2
|
+
trilogy/__init__.py,sha256=zj08OHuDZczNN19GmjEnVohqzVtTiDs1qMbzg8Lla3I,304
|
|
3
|
+
trilogy/constants.py,sha256=g_zkVCNjGop6coZ1kM8eXXAzCnUN22ldx3TYFz0E9sc,1747
|
|
4
4
|
trilogy/engine.py,sha256=3MiADf5MKcmxqiHBuRqiYdsXiLj7oitDfVvXvHrfjkA,2178
|
|
5
5
|
trilogy/executor.py,sha256=KgCAQhHPT-j0rPkBbALX0f84W9-Q-bkjHayGuavg99w,16490
|
|
6
6
|
trilogy/parser.py,sha256=o4cfk3j3yhUFoiDKq9ZX_GjBF3dKhDjXEwb63rcBkBM,293
|
|
@@ -28,43 +28,43 @@ trilogy/core/models/build_environment.py,sha256=mpx7MKGc60fnZLVdeLi2YSREy7eQbQYy
|
|
|
28
28
|
trilogy/core/models/core.py,sha256=iT9WdZoiXeglmUHWn6bZyXCTBpkApTGPKtNm_Mhbu_g,12987
|
|
29
29
|
trilogy/core/models/datasource.py,sha256=wogTevZ-9CyUW2a8gjzqMCieircxi-J5lkI7EOAZnck,9596
|
|
30
30
|
trilogy/core/models/environment.py,sha256=hwTIRnJgaHUdCYof7U5A9NPitGZ2s9yxqiW5O2SaJ9Y,28759
|
|
31
|
-
trilogy/core/models/execute.py,sha256=
|
|
31
|
+
trilogy/core/models/execute.py,sha256=pdL3voYB4dCQR_KMHwFaofP3ZpRbALRC2ELHueWyTko,42191
|
|
32
32
|
trilogy/core/optimizations/__init__.py,sha256=yspWc25M5SgAuvXYoSt5J8atyPbDlOfsKjIo5yGD9s4,368
|
|
33
33
|
trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnbnb6vdVIztUfFQZsGnM,513
|
|
34
34
|
trilogy/core/optimizations/hide_unused_concept.py,sha256=DbsP8NqQOxmPv9omDOoFNPUGObUkqsRRNrr5d1xDxx4,1962
|
|
35
35
|
trilogy/core/optimizations/inline_datasource.py,sha256=2sWNRpoRInnTgo9wExVT_r9RfLAQHI57reEV5cGHUcg,4329
|
|
36
|
-
trilogy/core/optimizations/predicate_pushdown.py,sha256=
|
|
36
|
+
trilogy/core/optimizations/predicate_pushdown.py,sha256=5ubatgq1IwWQ4L2FDt4--y168YLuGP-vwqH0m8IeTIw,9786
|
|
37
37
|
trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
-
trilogy/core/processing/concept_strategies_v3.py,sha256=
|
|
39
|
-
trilogy/core/processing/discovery_node_factory.py,sha256=
|
|
40
|
-
trilogy/core/processing/discovery_utility.py,sha256=
|
|
38
|
+
trilogy/core/processing/concept_strategies_v3.py,sha256=AcMU1d5uCo8I1PFCkBtmcC6iFmM9vN6xSdKxSVMGfpA,23080
|
|
39
|
+
trilogy/core/processing/discovery_node_factory.py,sha256=p23jiiHyhrW-Q8ndbnRlqMHJKT8ZqPOA89SzE4xaFFo,15445
|
|
40
|
+
trilogy/core/processing/discovery_utility.py,sha256=ZJTTWm34zpR30p-xzElUJCTdx-XT44skKG0-LBfEOg4,12525
|
|
41
41
|
trilogy/core/processing/discovery_validation.py,sha256=eZ4HfHMpqZLI8MGG2jez8arS8THs6ceuVrQFIY6gXrU,5364
|
|
42
42
|
trilogy/core/processing/graph_utils.py,sha256=8QUVrkE9j-9C1AyrCb1nQEh8daCe0u1HuXl-Te85lag,1205
|
|
43
|
-
trilogy/core/processing/utility.py,sha256=
|
|
43
|
+
trilogy/core/processing/utility.py,sha256=1_oNnk6lWiy-D7LKYr07kU_v7iAM4i6ITUAS4bIiCr4,23444
|
|
44
44
|
trilogy/core/processing/node_generators/__init__.py,sha256=iVJ-crowPxYeut-hFjyEjfibKIDq7PfB4LEuDAUCjGY,943
|
|
45
|
-
trilogy/core/processing/node_generators/basic_node.py,sha256=
|
|
46
|
-
trilogy/core/processing/node_generators/common.py,sha256=
|
|
45
|
+
trilogy/core/processing/node_generators/basic_node.py,sha256=74LoVZXLinRvSzk2LmI1kwza96TnuH3ELoYRIbHB29A,5578
|
|
46
|
+
trilogy/core/processing/node_generators/common.py,sha256=xF32Kf6B08dZgKs2SOow1HomptSiSC057GCUCHFlS5s,9464
|
|
47
47
|
trilogy/core/processing/node_generators/constant_node.py,sha256=LfpDq2WrBRZ3tGsLxw77LuigKfhbteWWh9L8BGdMGwk,1146
|
|
48
|
-
trilogy/core/processing/node_generators/filter_node.py,sha256=
|
|
49
|
-
trilogy/core/processing/node_generators/group_node.py,sha256=
|
|
48
|
+
trilogy/core/processing/node_generators/filter_node.py,sha256=cJ5od1fAfvalaUDO2O4Y6Yrr2RukOCqey7f3zrKSBbI,10808
|
|
49
|
+
trilogy/core/processing/node_generators/group_node.py,sha256=NdK1rl6Ze94XFWtgeC2dlRiL4pS3lh1ArKGPEltLtnw,8525
|
|
50
50
|
trilogy/core/processing/node_generators/group_to_node.py,sha256=jKcNCDOY6fNblrdZwaRU0sbUSr9H0moQbAxrGgX6iGA,3832
|
|
51
|
-
trilogy/core/processing/node_generators/multiselect_node.py,sha256=
|
|
52
|
-
trilogy/core/processing/node_generators/node_merge_node.py,sha256=
|
|
51
|
+
trilogy/core/processing/node_generators/multiselect_node.py,sha256=a505AEixjsjp5jI8Ng3H5KF_AaehkS6HfRfTef64l_o,7063
|
|
52
|
+
trilogy/core/processing/node_generators/node_merge_node.py,sha256=hNcZxnDLTZyYJWfojg769zH9HB9PfZfESmpN1lcHWXg,23172
|
|
53
53
|
trilogy/core/processing/node_generators/recursive_node.py,sha256=l5zdh0dURKwmAy8kK4OpMtZfyUEQRk6N-PwSWIyBpSM,2468
|
|
54
|
-
trilogy/core/processing/node_generators/rowset_node.py,sha256=
|
|
54
|
+
trilogy/core/processing/node_generators/rowset_node.py,sha256=MuVNIexXhqGONho_mewqMOwaYXNUnjjvyPvk_RDGNYE,5943
|
|
55
55
|
trilogy/core/processing/node_generators/select_merge_node.py,sha256=KQvGoNT5ZBWQ_caEomRTtG1PKZC7OPT4PKfY0QmwMGE,22270
|
|
56
56
|
trilogy/core/processing/node_generators/select_node.py,sha256=Ta1G39V94gjX_AgyZDz9OqnwLz4BjY3D6Drx9YpziMQ,3555
|
|
57
57
|
trilogy/core/processing/node_generators/synonym_node.py,sha256=AnAsa_Wj50NJ_IK0HSgab_7klYmKVrv0WI1uUe-GvEY,3766
|
|
58
58
|
trilogy/core/processing/node_generators/union_node.py,sha256=NxQbnRRoYMI4WjMeph41yk4E6yipj53qdGuNt-Mozxw,2818
|
|
59
|
-
trilogy/core/processing/node_generators/unnest_node.py,sha256=
|
|
59
|
+
trilogy/core/processing/node_generators/unnest_node.py,sha256=u_hVHFYMz-ZylDdHH9mhFSRpxuKcTGvrrOP0rxrY_Xg,3901
|
|
60
60
|
trilogy/core/processing/node_generators/window_node.py,sha256=A90linr4pkZtTNfn9k2YNLqrJ_SFII3lbHxB-BC6mI8,6688
|
|
61
61
|
trilogy/core/processing/node_generators/select_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
62
62
|
trilogy/core/processing/node_generators/select_helpers/datasource_injection.py,sha256=m2YQ4OmG0N2O61a7NEq1ZzbTa7JsCC00lxB2ymjcYRI,8224
|
|
63
63
|
trilogy/core/processing/nodes/__init__.py,sha256=zTge1EzwzEydlcMliIFO_TT7h7lS8l37lyZuQDir1h0,5487
|
|
64
|
-
trilogy/core/processing/nodes/base_node.py,sha256=
|
|
64
|
+
trilogy/core/processing/nodes/base_node.py,sha256=6LPQ5zP_dZJ6-k_dmX9ZSLsHaQMHgqiR5DEylpHYGZA,18478
|
|
65
65
|
trilogy/core/processing/nodes/filter_node.py,sha256=5VtRfKbCORx0dV-vQfgy3gOEkmmscL9f31ExvlODwvY,2461
|
|
66
|
-
trilogy/core/processing/nodes/group_node.py,sha256=
|
|
67
|
-
trilogy/core/processing/nodes/merge_node.py,sha256=
|
|
66
|
+
trilogy/core/processing/nodes/group_node.py,sha256=sKsRP_BWEKg6z63T1X5ZlkJF2IMif0IEbVWTk-cdOH8,7100
|
|
67
|
+
trilogy/core/processing/nodes/merge_node.py,sha256=uc0tlz30Yt9SnCwLhMcWuPVbXLzm3dzy0XqbyirqqTo,16521
|
|
68
68
|
trilogy/core/processing/nodes/recursive_node.py,sha256=k0rizxR8KE64ievfHx_GPfQmU8QAP118Laeyq5BLUOk,1526
|
|
69
69
|
trilogy/core/processing/nodes/select_node_v2.py,sha256=IWyKyNgFlV8A2S1FUTPdIaogg6PzaHh-HmQo6v24sbg,8862
|
|
70
70
|
trilogy/core/processing/nodes/union_node.py,sha256=hLAXXVWqEgMWi7dlgSHfCF59fon64av14-uPgJzoKzM,1870
|
|
@@ -82,7 +82,7 @@ trilogy/core/validation/datasource.py,sha256=nJeEFyb6iMBwlEVdYVy1vLzAbdRZwOsUjGx
|
|
|
82
82
|
trilogy/core/validation/environment.py,sha256=ymvhQyt7jLK641JAAIQkqjQaAmr9C5022ILzYvDgPP0,2835
|
|
83
83
|
trilogy/core/validation/fix.py,sha256=Z818UFNLxndMTLiyhB3doLxIfnOZ-16QGvVFWuD7UsA,3750
|
|
84
84
|
trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
85
|
-
trilogy/dialect/base.py,sha256=
|
|
85
|
+
trilogy/dialect/base.py,sha256=hFX0_3N-m3ZRTCyv1S650a8OPlx9qjp5Zh8wzTBx6E8,50338
|
|
86
86
|
trilogy/dialect/bigquery.py,sha256=XS3hpybeowgfrOrkycAigAF3NX2YUzTzfgE6f__2fT4,4316
|
|
87
87
|
trilogy/dialect/common.py,sha256=cUI7JMmpG_A5KcaxRI-GoyqwLMD6jTf0JJhgcOdwQK4,5833
|
|
88
88
|
trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
|
|
@@ -110,16 +110,17 @@ trilogy/parsing/trilogy.lark,sha256=6eBDD6d4D9N1Nnn4CtmaoB-NpOpjHrEn5oi0JykAlbE,
|
|
|
110
110
|
trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
111
111
|
trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
|
|
112
112
|
trilogy/std/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
113
|
+
trilogy/std/color.preql,sha256=sS9AXLDkECuDbNGnBMi2KnUuJukyVZVThKI9mP-ZOZI,50
|
|
113
114
|
trilogy/std/date.preql,sha256=HWZm4t4HWyxr5geWRsY05RnHBVDMci8z8YA2cu0-OOw,188
|
|
114
|
-
trilogy/std/display.preql,sha256=
|
|
115
|
+
trilogy/std/display.preql,sha256=ZJ08crsZnC3kaWwNUrMB1ZH5j6DUUbz8RaUgihA8sm4,299
|
|
115
116
|
trilogy/std/geography.preql,sha256=1A9Sq5PPMBnEPPf7f-rPVYxJfsnWpQ8oV_k4Fm3H2dU,675
|
|
116
117
|
trilogy/std/metric.preql,sha256=DRECGhkMyqfit5Fl4Ut9zbWrJuSMI1iO9HikuyoBpE0,421
|
|
117
118
|
trilogy/std/money.preql,sha256=XWwvAV3WxBsHX9zfptoYRnBigcfYwrYtBHXTME0xJuQ,2082
|
|
118
119
|
trilogy/std/net.preql,sha256=WZCuvH87_rZntZiuGJMmBDMVKkdhTtxeHOkrXNwJ1EE,416
|
|
119
120
|
trilogy/std/ranking.preql,sha256=LDoZrYyz4g3xsII9XwXfmstZD-_92i1Eox1UqkBIfi8,83
|
|
120
121
|
trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
|
|
121
|
-
pytrilogy-0.0.3.
|
|
122
|
-
pytrilogy-0.0.3.
|
|
123
|
-
pytrilogy-0.0.3.
|
|
124
|
-
pytrilogy-0.0.3.
|
|
125
|
-
pytrilogy-0.0.3.
|
|
122
|
+
pytrilogy-0.0.3.105.dist-info/METADATA,sha256=9mxttMp6pkvGQMM3noJhBtY-c0gugqilfPVh0pfo61E,11839
|
|
123
|
+
pytrilogy-0.0.3.105.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
124
|
+
pytrilogy-0.0.3.105.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
|
|
125
|
+
pytrilogy-0.0.3.105.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
126
|
+
pytrilogy-0.0.3.105.dist-info/RECORD,,
|
trilogy/__init__.py
CHANGED
trilogy/constants.py
CHANGED
trilogy/core/models/execute.py
CHANGED
|
@@ -711,8 +711,6 @@ class QueryDatasource(BaseModel):
|
|
|
711
711
|
f" {[c.address for c in self.output_concepts]} concepts and"
|
|
712
712
|
f" {other.name} with {[c.address for c in other.output_concepts]} concepts"
|
|
713
713
|
)
|
|
714
|
-
logger.info(self.source_map)
|
|
715
|
-
logger.info(other.source_map)
|
|
716
714
|
|
|
717
715
|
merged_datasources: dict[str, Union[BuildDatasource, "QueryDatasource"]] = {}
|
|
718
716
|
|
|
@@ -816,10 +814,7 @@ class QueryDatasource(BaseModel):
|
|
|
816
814
|
use_raw_name,
|
|
817
815
|
force_alias=force_alias,
|
|
818
816
|
)
|
|
819
|
-
except ValueError
|
|
820
|
-
from trilogy.constants import logger
|
|
821
|
-
|
|
822
|
-
logger.debug(e)
|
|
817
|
+
except ValueError:
|
|
823
818
|
continue
|
|
824
819
|
existing = [c.with_grain(self.grain) for c in self.output_concepts]
|
|
825
820
|
if concept in existing:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from trilogy.core.enums import (
|
|
2
2
|
BooleanOperator,
|
|
3
|
+
SourceType,
|
|
3
4
|
)
|
|
4
5
|
from trilogy.core.models.build import (
|
|
5
6
|
BuildComparison,
|
|
@@ -59,12 +60,19 @@ class PredicatePushdown(OptimizationRule):
|
|
|
59
60
|
)
|
|
60
61
|
return False
|
|
61
62
|
materialized = {k for k, v in parent_cte.source_map.items() if v != []}
|
|
63
|
+
|
|
62
64
|
if not row_conditions or not materialized:
|
|
63
65
|
return False
|
|
64
66
|
output_addresses = {x.address for x in parent_cte.output_columns}
|
|
65
67
|
# if any of the existence conditions are created on the asset, we can't push up to it
|
|
66
68
|
if existence_conditions and existence_conditions.intersection(output_addresses):
|
|
67
69
|
return False
|
|
70
|
+
if existence_conditions:
|
|
71
|
+
self.log(
|
|
72
|
+
f"Not pushing up existence {candidate} to {parent_cte.name} as it is a filter node"
|
|
73
|
+
)
|
|
74
|
+
if parent_cte.source.source_type == SourceType.FILTER:
|
|
75
|
+
return False
|
|
68
76
|
# if it's a root datasource, we can filter on _any_ of the output concepts
|
|
69
77
|
if parent_cte.is_root_datasource:
|
|
70
78
|
extra_check = {
|
|
@@ -81,7 +89,7 @@ class PredicatePushdown(OptimizationRule):
|
|
|
81
89
|
children = inverse_map.get(parent_cte.name, [])
|
|
82
90
|
if all([is_child_of(candidate, child.condition) for child in children]):
|
|
83
91
|
self.log(
|
|
84
|
-
f"All concepts
|
|
92
|
+
f"All concepts [{row_conditions}] and existence conditions [{existence_conditions}] not block pushup of [{output_addresses}]found on {parent_cte.name} with existing {parent_cte.condition} and all it's {len(children)} children include same filter; pushing up {candidate}"
|
|
85
93
|
)
|
|
86
94
|
if parent_cte.condition and not is_scalar_condition(
|
|
87
95
|
parent_cte.condition
|
|
@@ -19,7 +19,7 @@ from trilogy.core.processing.discovery_utility import (
|
|
|
19
19
|
LOGGER_PREFIX,
|
|
20
20
|
depth_to_prefix,
|
|
21
21
|
get_priority_concept,
|
|
22
|
-
|
|
22
|
+
group_if_required_v2,
|
|
23
23
|
)
|
|
24
24
|
from trilogy.core.processing.discovery_validation import (
|
|
25
25
|
ValidationResult,
|
|
@@ -66,7 +66,19 @@ def generate_candidates_restrictive(
|
|
|
66
66
|
|
|
67
67
|
# if it's single row, joins are irrelevant. Fetch without keys.
|
|
68
68
|
if priority_concept.granularity == Granularity.SINGLE_ROW:
|
|
69
|
-
|
|
69
|
+
logger.info("Have single row concept, including only other single row optional")
|
|
70
|
+
optional = (
|
|
71
|
+
[
|
|
72
|
+
x
|
|
73
|
+
for x in candidates
|
|
74
|
+
if x.granularity == Granularity.SINGLE_ROW
|
|
75
|
+
and x.address not in priority_concept.pseudonyms
|
|
76
|
+
and priority_concept.address not in x.pseudonyms
|
|
77
|
+
]
|
|
78
|
+
if priority_concept.derivation == Derivation.AGGREGATE
|
|
79
|
+
else []
|
|
80
|
+
)
|
|
81
|
+
return optional, conditions
|
|
70
82
|
|
|
71
83
|
if conditions and priority_concept.derivation in ROOT_DERIVATIONS:
|
|
72
84
|
logger.info(
|
|
@@ -374,15 +386,21 @@ def generate_loop_completion(context: LoopContext, virtual: set[str]) -> Strateg
|
|
|
374
386
|
logger.info(
|
|
375
387
|
f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Found different non-virtual output concepts ({non_virtual_difference_values}), removing condition injected values by setting outputs to {[x.address for x in output.output_concepts if x.address in non_virtual_output]}"
|
|
376
388
|
)
|
|
377
|
-
output.set_output_concepts(
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
389
|
+
# output.set_output_concepts(
|
|
390
|
+
# [
|
|
391
|
+
# x
|
|
392
|
+
# for x in output.output_concepts
|
|
393
|
+
# if x.address not in non_virtual_difference_values
|
|
394
|
+
# or any(c in non_virtual_output for c in x.pseudonyms)
|
|
395
|
+
# ],
|
|
396
|
+
# rebuild=True,
|
|
397
|
+
# change_visibility=False
|
|
398
|
+
# )
|
|
399
|
+
# output.set_output_concepts(context.original_mandatory)
|
|
400
|
+
|
|
401
|
+
# if isinstance(output, MergeNode):
|
|
402
|
+
# output.force_group = True
|
|
403
|
+
# output.rebuild_cache()
|
|
386
404
|
|
|
387
405
|
logger.info(
|
|
388
406
|
f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Source stack has single node, returning that {type(output)}"
|
|
@@ -416,14 +434,17 @@ def generate_loop_completion(context: LoopContext, virtual: set[str]) -> Strateg
|
|
|
416
434
|
logger.info(
|
|
417
435
|
f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Graph is connected, returning {type(output)} node output {[x.address for x in output.usable_outputs]} partial {[c.address for c in output.partial_concepts or []]} with {context.conditions}"
|
|
418
436
|
)
|
|
437
|
+
from trilogy.core.processing.discovery_utility import group_if_required_v2
|
|
438
|
+
|
|
419
439
|
if condition_required and context.conditions and non_virtual_different:
|
|
420
440
|
logger.info(
|
|
421
441
|
f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Conditions {context.conditions} were injected, checking if we need a group to restore grain"
|
|
422
442
|
)
|
|
423
|
-
return
|
|
443
|
+
return group_if_required_v2(
|
|
424
444
|
output, context.original_mandatory, context.environment
|
|
425
445
|
)
|
|
426
|
-
|
|
446
|
+
|
|
447
|
+
return group_if_required_v2(output, context.original_mandatory, context.environment)
|
|
427
448
|
|
|
428
449
|
|
|
429
450
|
def _search_concepts(
|
|
@@ -588,4 +609,4 @@ def source_query_concepts(
|
|
|
588
609
|
logger.info(
|
|
589
610
|
f"{depth_to_prefix(0)}{LOGGER_PREFIX} final concepts are {[x.address for x in final]}"
|
|
590
611
|
)
|
|
591
|
-
return
|
|
612
|
+
return group_if_required_v2(root, output_concepts, environment)
|
|
@@ -177,7 +177,12 @@ def _generate_union_node(ctx: NodeGenerationContext) -> StrategyNode | None:
|
|
|
177
177
|
def _generate_aggregate_node(ctx: NodeGenerationContext) -> StrategyNode | None:
|
|
178
178
|
# Filter out constants to avoid multiplication issues
|
|
179
179
|
agg_optional = [
|
|
180
|
-
x
|
|
180
|
+
x
|
|
181
|
+
for x in ctx.local_optional
|
|
182
|
+
if not (
|
|
183
|
+
x.granularity == Granularity.SINGLE_ROW
|
|
184
|
+
and x.derivation != Derivation.AGGREGATE
|
|
185
|
+
)
|
|
181
186
|
]
|
|
182
187
|
|
|
183
188
|
logger.info(
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
3
|
from trilogy.constants import logger
|
|
4
|
-
from trilogy.core.enums import Derivation
|
|
4
|
+
from trilogy.core.enums import Derivation, Purpose
|
|
5
5
|
from trilogy.core.models.build import (
|
|
6
6
|
BuildConcept,
|
|
7
|
+
BuildDatasource,
|
|
8
|
+
BuildFilterItem,
|
|
9
|
+
BuildGrain,
|
|
7
10
|
BuildRowsetItem,
|
|
8
11
|
)
|
|
9
12
|
from trilogy.core.models.build_environment import BuildEnvironment
|
|
13
|
+
from trilogy.core.models.execute import QueryDatasource, UnnestJoin
|
|
10
14
|
from trilogy.core.processing.nodes import GroupNode, MergeNode, StrategyNode
|
|
15
|
+
from trilogy.core.processing.utility import GroupRequiredResponse
|
|
11
16
|
|
|
12
17
|
|
|
13
18
|
def depth_to_prefix(depth: int) -> str:
|
|
@@ -17,31 +22,197 @@ def depth_to_prefix(depth: int) -> str:
|
|
|
17
22
|
LOGGER_PREFIX = "[DISCOVERY LOOP]"
|
|
18
23
|
|
|
19
24
|
|
|
20
|
-
def
|
|
25
|
+
def calculate_effective_parent_grain(
|
|
26
|
+
node: QueryDatasource | BuildDatasource,
|
|
27
|
+
) -> BuildGrain:
|
|
28
|
+
# calculate the effective grain of the parent node
|
|
29
|
+
# this is the union of all parent grains
|
|
30
|
+
if isinstance(node, QueryDatasource):
|
|
31
|
+
grain = BuildGrain()
|
|
32
|
+
qds = node
|
|
33
|
+
if not qds.joins:
|
|
34
|
+
return qds.datasources[0].grain
|
|
35
|
+
seen = set()
|
|
36
|
+
for join in qds.joins:
|
|
37
|
+
if isinstance(join, UnnestJoin):
|
|
38
|
+
grain += BuildGrain(components=set([x.address for x in join.concepts]))
|
|
39
|
+
continue
|
|
40
|
+
pairs = join.concept_pairs or []
|
|
41
|
+
for key in pairs:
|
|
42
|
+
left = key.existing_datasource
|
|
43
|
+
logger.info(f"adding left grain {left.grain} for join key {key.left}")
|
|
44
|
+
grain += left.grain
|
|
45
|
+
seen.add(left.name)
|
|
46
|
+
keys = [key.right for key in pairs]
|
|
47
|
+
join_grain = BuildGrain.from_concepts(keys)
|
|
48
|
+
if join_grain == join.right_datasource.grain:
|
|
49
|
+
logger.info(f"irrelevant right join {join}, does not change grain")
|
|
50
|
+
else:
|
|
51
|
+
logger.info(
|
|
52
|
+
f"join changes grain, adding {join.right_datasource.grain} to {grain}"
|
|
53
|
+
)
|
|
54
|
+
grain += join.right_datasource.grain
|
|
55
|
+
seen.add(join.right_datasource.name)
|
|
56
|
+
for x in qds.datasources:
|
|
57
|
+
# if we haven't seen it, it's still contributing to grain
|
|
58
|
+
# unless used ONLY in a subselect
|
|
59
|
+
# so the existence check is a [bad] proxy for that
|
|
60
|
+
if x.name not in seen and not (
|
|
61
|
+
qds.condition
|
|
62
|
+
and qds.condition.existence_arguments
|
|
63
|
+
and any(
|
|
64
|
+
[
|
|
65
|
+
c.address in block
|
|
66
|
+
for c in x.output_concepts
|
|
67
|
+
for block in qds.condition.existence_arguments
|
|
68
|
+
]
|
|
69
|
+
)
|
|
70
|
+
):
|
|
71
|
+
logger.info(f"adding unjoined grain {x.grain} for datasource {x.name}")
|
|
72
|
+
grain += x.grain
|
|
73
|
+
return grain
|
|
74
|
+
else:
|
|
75
|
+
return node.grain or BuildGrain()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def check_if_group_required(
|
|
79
|
+
downstream_concepts: List[BuildConcept],
|
|
80
|
+
parents: list[QueryDatasource | BuildDatasource],
|
|
81
|
+
environment: BuildEnvironment,
|
|
82
|
+
depth: int = 0,
|
|
83
|
+
) -> GroupRequiredResponse:
|
|
84
|
+
padding = "\t" * depth
|
|
85
|
+
target_grain = BuildGrain.from_concepts(
|
|
86
|
+
downstream_concepts,
|
|
87
|
+
environment=environment,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
comp_grain = BuildGrain()
|
|
91
|
+
for source in parents:
|
|
92
|
+
# comp_grain += source.grain
|
|
93
|
+
comp_grain += calculate_effective_parent_grain(source)
|
|
94
|
+
|
|
95
|
+
# dynamically select if we need to group
|
|
96
|
+
# we must avoid grouping if we are already at grain
|
|
97
|
+
if comp_grain.issubset(target_grain):
|
|
98
|
+
|
|
99
|
+
logger.info(
|
|
100
|
+
f"{padding}{LOGGER_PREFIX} Group requirement check: {comp_grain}, target: {target_grain}, grain is subset of target, no group node required"
|
|
101
|
+
)
|
|
102
|
+
return GroupRequiredResponse(target_grain, comp_grain, False)
|
|
103
|
+
# find out what extra is in the comp grain vs target grain
|
|
104
|
+
difference = [
|
|
105
|
+
environment.concepts[c] for c in (comp_grain - target_grain).components
|
|
106
|
+
]
|
|
107
|
+
logger.info(
|
|
108
|
+
f"{padding}{LOGGER_PREFIX} Group requirement check: upstream grain: {comp_grain}, desired grain: {target_grain} from , difference {[x.address for x in difference]}"
|
|
109
|
+
)
|
|
110
|
+
for x in difference:
|
|
111
|
+
logger.info(
|
|
112
|
+
f"{padding}{LOGGER_PREFIX} Difference concept {x.address} purpose {x.purpose} keys {x.keys}"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# if the difference is all unique properties whose keys are in the source grain
|
|
116
|
+
# we can also suppress the group
|
|
117
|
+
if all(
|
|
118
|
+
[
|
|
119
|
+
x.keys
|
|
120
|
+
and all(
|
|
121
|
+
environment.concepts[z].address in comp_grain.components for z in x.keys
|
|
122
|
+
)
|
|
123
|
+
for x in difference
|
|
124
|
+
]
|
|
125
|
+
):
|
|
126
|
+
logger.info(
|
|
127
|
+
f"{padding}{LOGGER_PREFIX} Group requirement check: skipped due to unique property validation"
|
|
128
|
+
)
|
|
129
|
+
return GroupRequiredResponse(target_grain, comp_grain, False)
|
|
130
|
+
if all([x.purpose == Purpose.KEY for x in difference]):
|
|
131
|
+
logger.info(
|
|
132
|
+
f"{padding}{LOGGER_PREFIX} checking if downstream is unique properties of key"
|
|
133
|
+
)
|
|
134
|
+
replaced_grain_raw: list[set[str]] = [
|
|
135
|
+
(
|
|
136
|
+
x.keys or set()
|
|
137
|
+
if x.purpose == Purpose.UNIQUE_PROPERTY
|
|
138
|
+
else set([x.address])
|
|
139
|
+
)
|
|
140
|
+
for x in downstream_concepts
|
|
141
|
+
if x.address in target_grain.components
|
|
142
|
+
]
|
|
143
|
+
# flatten the list of lists
|
|
144
|
+
replaced_grain = [item for sublist in replaced_grain_raw for item in sublist]
|
|
145
|
+
# if the replaced grain is a subset of the comp grain, we can skip the group
|
|
146
|
+
unique_grain_comp = BuildGrain.from_concepts(
|
|
147
|
+
replaced_grain, environment=environment
|
|
148
|
+
)
|
|
149
|
+
if comp_grain.issubset(unique_grain_comp):
|
|
150
|
+
logger.info(
|
|
151
|
+
f"{padding}{LOGGER_PREFIX} Group requirement check: skipped due to unique property validation"
|
|
152
|
+
)
|
|
153
|
+
return GroupRequiredResponse(target_grain, comp_grain, False)
|
|
154
|
+
logger.info(
|
|
155
|
+
f"{padding}{LOGGER_PREFIX} Checking for grain equivalence for filters and rowsets"
|
|
156
|
+
)
|
|
157
|
+
ngrain = []
|
|
158
|
+
for con in target_grain.components:
|
|
159
|
+
full = environment.concepts[con]
|
|
160
|
+
if full.derivation == Derivation.ROWSET:
|
|
161
|
+
ngrain.append(full.address.split(".", 1)[1])
|
|
162
|
+
elif full.derivation == Derivation.FILTER:
|
|
163
|
+
assert isinstance(full.lineage, BuildFilterItem)
|
|
164
|
+
if isinstance(full.lineage.content, BuildConcept):
|
|
165
|
+
ngrain.append(full.lineage.content.address)
|
|
166
|
+
else:
|
|
167
|
+
ngrain.append(full.address)
|
|
168
|
+
target_grain2 = BuildGrain.from_concepts(
|
|
169
|
+
ngrain,
|
|
170
|
+
environment=environment,
|
|
171
|
+
)
|
|
172
|
+
if comp_grain.issubset(target_grain2):
|
|
173
|
+
logger.info(
|
|
174
|
+
f"{padding}{LOGGER_PREFIX} Group requirement check: {comp_grain}, {target_grain2}, pre rowset grain is subset of target, no group node required"
|
|
175
|
+
)
|
|
176
|
+
return GroupRequiredResponse(target_grain2, comp_grain, False)
|
|
177
|
+
|
|
178
|
+
logger.info(f"{padding}{LOGGER_PREFIX} Group requirement check: group required")
|
|
179
|
+
return GroupRequiredResponse(target_grain, comp_grain, True)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def group_if_required_v2(
|
|
21
183
|
root: StrategyNode, final: List[BuildConcept], environment: BuildEnvironment
|
|
22
184
|
):
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
185
|
+
required = check_if_group_required(
|
|
186
|
+
downstream_concepts=final, parents=[root.resolve()], environment=environment
|
|
187
|
+
)
|
|
188
|
+
targets = [
|
|
189
|
+
x
|
|
190
|
+
for x in root.output_concepts
|
|
191
|
+
if x.address in final or any(c in final for c in x.pseudonyms)
|
|
192
|
+
]
|
|
193
|
+
if required.required:
|
|
32
194
|
if isinstance(root, MergeNode):
|
|
33
195
|
root.force_group = True
|
|
34
|
-
root.set_output_concepts(
|
|
196
|
+
root.set_output_concepts(targets, rebuild=False, change_visibility=False)
|
|
35
197
|
root.rebuild_cache()
|
|
36
198
|
return root
|
|
199
|
+
elif isinstance(root, GroupNode):
|
|
200
|
+
# root.set_output_concepts(final, rebuild=False)
|
|
201
|
+
# root.rebuild_cache()
|
|
202
|
+
return root
|
|
37
203
|
return GroupNode(
|
|
38
|
-
output_concepts=
|
|
39
|
-
input_concepts=
|
|
204
|
+
output_concepts=targets,
|
|
205
|
+
input_concepts=targets,
|
|
40
206
|
environment=environment,
|
|
41
207
|
parents=[root],
|
|
42
208
|
partial_concepts=root.partial_concepts,
|
|
43
209
|
preexisting_conditions=root.preexisting_conditions,
|
|
44
210
|
)
|
|
211
|
+
elif isinstance(root, GroupNode):
|
|
212
|
+
|
|
213
|
+
return root
|
|
214
|
+
else:
|
|
215
|
+
root.set_output_concepts(targets, rebuild=False, change_visibility=False)
|
|
45
216
|
return root
|
|
46
217
|
|
|
47
218
|
|
|
@@ -143,4 +143,5 @@ def gen_basic_node(
|
|
|
143
143
|
logger.info(
|
|
144
144
|
f"{depth_prefix}{LOGGER_PREFIX} Returning basic select for {concept}: input: {[x.address for x in parent_node.input_concepts]} output {[x.address for x in parent_node.output_concepts]} hidden {[x for x in parent_node.hidden_concepts]}"
|
|
145
145
|
)
|
|
146
|
+
|
|
146
147
|
return parent_node
|
|
@@ -4,7 +4,6 @@ from trilogy.constants import logger
|
|
|
4
4
|
from trilogy.core.models.build import (
|
|
5
5
|
BuildConcept,
|
|
6
6
|
BuildFilterItem,
|
|
7
|
-
BuildGrain,
|
|
8
7
|
BuildWhereClause,
|
|
9
8
|
)
|
|
10
9
|
from trilogy.core.models.build_environment import BuildEnvironment
|
|
@@ -238,21 +237,14 @@ def gen_filter_node(
|
|
|
238
237
|
if not parent.preexisting_conditions == where.conditional:
|
|
239
238
|
parent.add_condition(where.conditional)
|
|
240
239
|
parent.add_existence_concepts(flattened_existence, False)
|
|
241
|
-
parent.grain = BuildGrain.from_concepts(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
)
|
|
240
|
+
# parent.grain = BuildGrain.from_concepts(
|
|
241
|
+
# parent.output_concepts,
|
|
242
|
+
# environment=environment,
|
|
243
|
+
# )
|
|
245
244
|
parent.rebuild_cache()
|
|
246
245
|
filter_node = parent
|
|
247
246
|
else:
|
|
248
247
|
core_parent_nodes.append(row_parent)
|
|
249
|
-
filters = [concept] + same_filter_optional
|
|
250
|
-
parents_for_grain = [
|
|
251
|
-
x.lineage.content
|
|
252
|
-
for x in filters
|
|
253
|
-
if isinstance(x.lineage, BuildFilterItem)
|
|
254
|
-
and isinstance(x.lineage.content, BuildConcept)
|
|
255
|
-
]
|
|
256
248
|
filter_node = FilterNode(
|
|
257
249
|
input_concepts=unique(
|
|
258
250
|
parent_row_concepts + flattened_existence,
|
|
@@ -261,9 +253,6 @@ def gen_filter_node(
|
|
|
261
253
|
output_concepts=[concept] + same_filter_optional + parent_row_concepts,
|
|
262
254
|
environment=environment,
|
|
263
255
|
parents=core_parent_nodes,
|
|
264
|
-
grain=BuildGrain.from_concepts(
|
|
265
|
-
parents_for_grain + parent_row_concepts, environment=environment
|
|
266
|
-
),
|
|
267
256
|
preexisting_conditions=conditions.conditional if conditions else None,
|
|
268
257
|
)
|
|
269
258
|
|
|
@@ -108,6 +108,42 @@ def gen_group_node(
|
|
|
108
108
|
logger.info(
|
|
109
109
|
f"{padding(depth)}{LOGGER_PREFIX} cannot include optional agg {possible_agg.address}; it has mismatched parent grain {comp_grain } vs local parent {build_grain_parents}"
|
|
110
110
|
)
|
|
111
|
+
elif concept.grain.abstract:
|
|
112
|
+
for possible_agg in local_optional:
|
|
113
|
+
if not isinstance(
|
|
114
|
+
possible_agg.lineage,
|
|
115
|
+
(BuildAggregateWrapper, BuildFunction),
|
|
116
|
+
):
|
|
117
|
+
|
|
118
|
+
continue
|
|
119
|
+
logger.info(
|
|
120
|
+
f"{padding(depth)}{LOGGER_PREFIX} considering optional agg {possible_agg.address} for {concept.address}"
|
|
121
|
+
)
|
|
122
|
+
agg_parents = resolve_function_parent_concepts(
|
|
123
|
+
possible_agg,
|
|
124
|
+
environment=environment,
|
|
125
|
+
)
|
|
126
|
+
comp_grain = get_aggregate_grain(possible_agg, environment)
|
|
127
|
+
if not possible_agg.grain.abstract:
|
|
128
|
+
continue
|
|
129
|
+
if set([x.address for x in agg_parents]).issubset(
|
|
130
|
+
set([x.address for x in parent_concepts])
|
|
131
|
+
):
|
|
132
|
+
output_concepts.append(possible_agg)
|
|
133
|
+
logger.info(
|
|
134
|
+
f"{padding(depth)}{LOGGER_PREFIX} found equivalent group by optional concept {possible_agg.address} for {concept.address}"
|
|
135
|
+
)
|
|
136
|
+
elif comp_grain == get_aggregate_grain(concept, environment):
|
|
137
|
+
extra = [x for x in agg_parents if x.address not in parent_concepts]
|
|
138
|
+
parent_concepts += extra
|
|
139
|
+
output_concepts.append(possible_agg)
|
|
140
|
+
logger.info(
|
|
141
|
+
f"{padding(depth)}{LOGGER_PREFIX} found equivalent group by optional concept {possible_agg.address} for {concept.address}"
|
|
142
|
+
)
|
|
143
|
+
else:
|
|
144
|
+
logger.info(
|
|
145
|
+
f"{padding(depth)}{LOGGER_PREFIX} cannot include optional agg {possible_agg.address}; it has mismatched parent grain {comp_grain } vs local parent {get_aggregate_grain(concept, environment)}"
|
|
146
|
+
)
|
|
111
147
|
if parent_concepts:
|
|
112
148
|
target_grain = BuildGrain.from_concepts(parent_concepts)
|
|
113
149
|
logger.info(
|
|
@@ -156,7 +156,7 @@ def gen_multiselect_node(
|
|
|
156
156
|
possible_joins = concept_to_relevant_joins(additional_relevant)
|
|
157
157
|
if not local_optional:
|
|
158
158
|
logger.info(
|
|
159
|
-
f"{padding(depth)}{LOGGER_PREFIX} no
|
|
159
|
+
f"{padding(depth)}{LOGGER_PREFIX} no enrichment required for rowset node; exiting early"
|
|
160
160
|
)
|
|
161
161
|
return node
|
|
162
162
|
if not possible_joins:
|
|
@@ -20,9 +20,6 @@ from trilogy.core.models.build import (
|
|
|
20
20
|
BuildWhereClause,
|
|
21
21
|
)
|
|
22
22
|
from trilogy.core.models.build_environment import BuildEnvironment
|
|
23
|
-
from trilogy.core.processing.discovery_utility import (
|
|
24
|
-
group_if_required,
|
|
25
|
-
)
|
|
26
23
|
from trilogy.core.processing.nodes import History, MergeNode, StrategyNode
|
|
27
24
|
from trilogy.core.processing.utility import padding
|
|
28
25
|
from trilogy.utility import unique
|
|
@@ -510,7 +507,7 @@ def subgraphs_to_merge_node(
|
|
|
510
507
|
search_conditions: BuildWhereClause | None = None,
|
|
511
508
|
enable_early_exit: bool = True,
|
|
512
509
|
):
|
|
513
|
-
|
|
510
|
+
|
|
514
511
|
parents: List[StrategyNode] = []
|
|
515
512
|
logger.info(
|
|
516
513
|
f"{padding(depth)}{LOGGER_PREFIX} fetching subgraphs {[[c.address for c in subgraph] for subgraph in concept_subgraphs]}"
|
|
@@ -555,7 +552,7 @@ def subgraphs_to_merge_node(
|
|
|
555
552
|
f"{padding(depth)}{LOGGER_PREFIX} only one parent node, exiting early w/ {[c.address for c in parents[0].output_concepts]}"
|
|
556
553
|
)
|
|
557
554
|
parent = parents[0]
|
|
558
|
-
return
|
|
555
|
+
return parent
|
|
559
556
|
|
|
560
557
|
rval = MergeNode(
|
|
561
558
|
input_concepts=unique(input_c, "address"),
|
|
@@ -563,7 +560,6 @@ def subgraphs_to_merge_node(
|
|
|
563
560
|
environment=environment,
|
|
564
561
|
parents=parents,
|
|
565
562
|
depth=depth,
|
|
566
|
-
grain=target_grain,
|
|
567
563
|
# hidden_concepts=[]
|
|
568
564
|
# conditions=conditions,
|
|
569
565
|
# conditions=search_conditions.conditional,
|
|
@@ -92,7 +92,7 @@ def gen_rowset_node(
|
|
|
92
92
|
|
|
93
93
|
node.rebuild_cache()
|
|
94
94
|
logger.info(
|
|
95
|
-
f"{padding(depth)}{LOGGER_PREFIX} final output is {[x.address for x in node.output_concepts]}"
|
|
95
|
+
f"{padding(depth)}{LOGGER_PREFIX} final output is {[x.address for x in node.output_concepts]} with grain {node.grain}"
|
|
96
96
|
)
|
|
97
97
|
if not local_optional or all(
|
|
98
98
|
(
|
|
@@ -4,7 +4,6 @@ from trilogy.constants import logger
|
|
|
4
4
|
from trilogy.core.models.build import (
|
|
5
5
|
BuildConcept,
|
|
6
6
|
BuildFunction,
|
|
7
|
-
BuildGrain,
|
|
8
7
|
BuildWhereClause,
|
|
9
8
|
)
|
|
10
9
|
from trilogy.core.models.build_environment import BuildEnvironment
|
|
@@ -104,10 +103,6 @@ def gen_unnest_node(
|
|
|
104
103
|
preexisting_conditions=(
|
|
105
104
|
conditional if conditional and local_conditions is False else None
|
|
106
105
|
),
|
|
107
|
-
grain=BuildGrain.from_concepts(
|
|
108
|
-
concepts=base.output_concepts,
|
|
109
|
-
environment=environment,
|
|
110
|
-
),
|
|
111
106
|
)
|
|
112
107
|
# qds = new.resolve()
|
|
113
108
|
# assert qds.source_map[concept.address] == {base.resolve()}
|
|
@@ -285,12 +285,23 @@ class StrategyNode:
|
|
|
285
285
|
self.rebuild_cache()
|
|
286
286
|
return self
|
|
287
287
|
|
|
288
|
-
def
|
|
288
|
+
def set_visible_concepts(self, concepts: List[BuildConcept]):
|
|
289
|
+
for x in self.output_concepts:
|
|
290
|
+
if x.address not in [c.address for c in concepts]:
|
|
291
|
+
self.hidden_concepts.add(x.address)
|
|
292
|
+
return self
|
|
293
|
+
|
|
294
|
+
def set_output_concepts(
|
|
295
|
+
self,
|
|
296
|
+
concepts: List[BuildConcept],
|
|
297
|
+
rebuild: bool = True,
|
|
298
|
+
change_visibility: bool = True,
|
|
299
|
+
):
|
|
289
300
|
# exit if no changes
|
|
290
301
|
if self.output_concepts == concepts:
|
|
291
302
|
return self
|
|
292
303
|
self.output_concepts = concepts
|
|
293
|
-
if self.hidden_concepts:
|
|
304
|
+
if self.hidden_concepts and change_visibility:
|
|
294
305
|
self.hidden_concepts = set(
|
|
295
306
|
x for x in self.hidden_concepts if x not in concepts
|
|
296
307
|
)
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
1
|
from typing import List, Optional
|
|
3
2
|
|
|
4
3
|
from trilogy.constants import logger
|
|
5
|
-
from trilogy.core.enums import
|
|
4
|
+
from trilogy.core.enums import SourceType
|
|
6
5
|
from trilogy.core.models.build import (
|
|
7
6
|
BuildComparison,
|
|
8
7
|
BuildConcept,
|
|
9
8
|
BuildConditional,
|
|
10
9
|
BuildDatasource,
|
|
11
|
-
BuildGrain,
|
|
12
10
|
BuildOrderBy,
|
|
13
11
|
BuildParenthetical,
|
|
14
12
|
)
|
|
@@ -18,19 +16,16 @@ from trilogy.core.processing.nodes.base_node import (
|
|
|
18
16
|
StrategyNode,
|
|
19
17
|
resolve_concept_map,
|
|
20
18
|
)
|
|
21
|
-
from trilogy.core.processing.utility import
|
|
19
|
+
from trilogy.core.processing.utility import (
|
|
20
|
+
GroupRequiredResponse,
|
|
21
|
+
find_nullable_concepts,
|
|
22
|
+
is_scalar_condition,
|
|
23
|
+
)
|
|
22
24
|
from trilogy.utility import unique
|
|
23
25
|
|
|
24
26
|
LOGGER_PREFIX = "[CONCEPT DETAIL - GROUP NODE]"
|
|
25
27
|
|
|
26
28
|
|
|
27
|
-
@dataclass
|
|
28
|
-
class GroupRequiredResponse:
|
|
29
|
-
target: BuildGrain
|
|
30
|
-
upstream: BuildGrain
|
|
31
|
-
required: bool
|
|
32
|
-
|
|
33
|
-
|
|
34
29
|
class GroupNode(StrategyNode):
|
|
35
30
|
source_type = SourceType.GROUP
|
|
36
31
|
|
|
@@ -80,92 +75,15 @@ class GroupNode(StrategyNode):
|
|
|
80
75
|
environment: BuildEnvironment,
|
|
81
76
|
depth: int = 0,
|
|
82
77
|
) -> GroupRequiredResponse:
|
|
83
|
-
|
|
84
|
-
target_grain = BuildGrain.from_concepts(
|
|
85
|
-
downstream_concepts,
|
|
86
|
-
environment=environment,
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
# the concepts of the souce grain might not exist in the output environment
|
|
90
|
-
# so we need to construct a new
|
|
91
|
-
concept_map: dict[str, BuildConcept] = {}
|
|
92
|
-
comp_grain = BuildGrain()
|
|
93
|
-
for source in parents:
|
|
94
|
-
comp_grain += source.grain
|
|
95
|
-
for x in source.output_concepts:
|
|
96
|
-
concept_map[x.address] = x
|
|
97
|
-
lookups: list[BuildConcept | str] = [
|
|
98
|
-
concept_map[x] if x in concept_map else x for x in comp_grain.components
|
|
99
|
-
]
|
|
100
|
-
|
|
101
|
-
comp_grain = BuildGrain.from_concepts(lookups, environment=environment)
|
|
102
|
-
|
|
103
|
-
# dynamically select if we need to group
|
|
104
|
-
# because sometimes, we are already at required grain
|
|
105
|
-
if comp_grain.issubset(target_grain):
|
|
106
|
-
|
|
107
|
-
logger.info(
|
|
108
|
-
f"{padding}{LOGGER_PREFIX} Group requirement check: {comp_grain}, {target_grain}, grain is subset of target, no group node required"
|
|
109
|
-
)
|
|
110
|
-
return GroupRequiredResponse(target_grain, comp_grain, False)
|
|
111
|
-
# find out what extra is in the comp grain vs target grain
|
|
112
|
-
difference = [
|
|
113
|
-
environment.concepts[c] for c in (comp_grain - target_grain).components
|
|
114
|
-
]
|
|
115
|
-
logger.info(
|
|
116
|
-
f"{padding}{LOGGER_PREFIX} Group requirement check: {comp_grain}, {target_grain}, difference {[x.address for x in difference]}"
|
|
117
|
-
)
|
|
78
|
+
from trilogy.core.processing.discovery_utility import check_if_group_required
|
|
118
79
|
|
|
119
|
-
|
|
120
|
-
# we can also suppress the group
|
|
121
|
-
if all(
|
|
122
|
-
[
|
|
123
|
-
x.keys
|
|
124
|
-
and all(
|
|
125
|
-
environment.concepts[z].address in comp_grain.components
|
|
126
|
-
for z in x.keys
|
|
127
|
-
)
|
|
128
|
-
for x in difference
|
|
129
|
-
]
|
|
130
|
-
):
|
|
131
|
-
logger.info(
|
|
132
|
-
f"{padding}{LOGGER_PREFIX} Group requirement check: skipped due to unique property validation"
|
|
133
|
-
)
|
|
134
|
-
return GroupRequiredResponse(target_grain, comp_grain, False)
|
|
135
|
-
if all([x.purpose == Purpose.KEY for x in difference]):
|
|
136
|
-
logger.info(
|
|
137
|
-
f"{padding}{LOGGER_PREFIX} checking if downstream is unique properties of key"
|
|
138
|
-
)
|
|
139
|
-
replaced_grain_raw: list[set[str]] = [
|
|
140
|
-
(
|
|
141
|
-
x.keys or set()
|
|
142
|
-
if x.purpose == Purpose.UNIQUE_PROPERTY
|
|
143
|
-
else set([x.address])
|
|
144
|
-
)
|
|
145
|
-
for x in downstream_concepts
|
|
146
|
-
if x.address in target_grain.components
|
|
147
|
-
]
|
|
148
|
-
# flatten the list of lists
|
|
149
|
-
replaced_grain = [
|
|
150
|
-
item for sublist in replaced_grain_raw for item in sublist
|
|
151
|
-
]
|
|
152
|
-
# if the replaced grain is a subset of the comp grain, we can skip the group
|
|
153
|
-
unique_grain_comp = BuildGrain.from_concepts(
|
|
154
|
-
replaced_grain, environment=environment
|
|
155
|
-
)
|
|
156
|
-
if comp_grain.issubset(unique_grain_comp):
|
|
157
|
-
logger.info(
|
|
158
|
-
f"{padding}{LOGGER_PREFIX} Group requirement check: skipped due to unique property validation"
|
|
159
|
-
)
|
|
160
|
-
return GroupRequiredResponse(target_grain, comp_grain, False)
|
|
161
|
-
|
|
162
|
-
logger.info(f"{padding}{LOGGER_PREFIX} Group requirement check: group required")
|
|
163
|
-
return GroupRequiredResponse(target_grain, comp_grain, True)
|
|
80
|
+
return check_if_group_required(downstream_concepts, parents, environment, depth)
|
|
164
81
|
|
|
165
82
|
def _resolve(self) -> QueryDatasource:
|
|
166
83
|
parent_sources: List[QueryDatasource | BuildDatasource] = [
|
|
167
84
|
p.resolve() for p in self.parents
|
|
168
85
|
]
|
|
86
|
+
|
|
169
87
|
grains = self.check_if_required(
|
|
170
88
|
self.output_concepts, parent_sources, self.environment, self.depth
|
|
171
89
|
)
|
|
@@ -341,6 +341,7 @@ class MergeNode(StrategyNode):
|
|
|
341
341
|
full_join_concepts += join.input_concepts
|
|
342
342
|
|
|
343
343
|
if self.force_group is True:
|
|
344
|
+
|
|
344
345
|
force_group = True
|
|
345
346
|
elif self.whole_grain:
|
|
346
347
|
force_group = False
|
|
@@ -367,6 +368,14 @@ class MergeNode(StrategyNode):
|
|
|
367
368
|
nullable_concepts = find_nullable_concepts(
|
|
368
369
|
source_map=source_map, joins=joins, datasources=final_datasets
|
|
369
370
|
)
|
|
371
|
+
if force_group:
|
|
372
|
+
|
|
373
|
+
grain = BuildGrain.from_concepts(
|
|
374
|
+
self.output_concepts, environment=self.environment
|
|
375
|
+
)
|
|
376
|
+
logger.info(
|
|
377
|
+
f"{self.logging_prefix}{LOGGER_PREFIX} forcing group by to achieve grain {grain}"
|
|
378
|
+
)
|
|
370
379
|
qds = QueryDatasource(
|
|
371
380
|
input_concepts=unique(self.input_concepts, "address"),
|
|
372
381
|
output_concepts=unique(self.output_concepts, "address"),
|
|
@@ -27,6 +27,7 @@ from trilogy.core.models.build import (
|
|
|
27
27
|
BuildDatasource,
|
|
28
28
|
BuildFilterItem,
|
|
29
29
|
BuildFunction,
|
|
30
|
+
BuildGrain,
|
|
30
31
|
BuildParenthetical,
|
|
31
32
|
BuildSubselectComparison,
|
|
32
33
|
BuildWindowItem,
|
|
@@ -82,6 +83,13 @@ class JoinOrderOutput:
|
|
|
82
83
|
return set(self.keys.keys())
|
|
83
84
|
|
|
84
85
|
|
|
86
|
+
@dataclass
|
|
87
|
+
class GroupRequiredResponse:
|
|
88
|
+
target: BuildGrain
|
|
89
|
+
upstream: BuildGrain
|
|
90
|
+
required: bool
|
|
91
|
+
|
|
92
|
+
|
|
85
93
|
def resolve_join_order_v2(
|
|
86
94
|
g: nx.Graph, partials: dict[str, list[str]], nullables: dict[str, list[str]]
|
|
87
95
|
) -> list[JoinOrderOutput]:
|
trilogy/dialect/base.py
CHANGED
|
@@ -786,6 +786,8 @@ class BaseDialect:
|
|
|
786
786
|
return str(e.value)
|
|
787
787
|
elif isinstance(e, ArrayType):
|
|
788
788
|
return f"{self.COMPLEX_DATATYPE_MAP[DataType.ARRAY](self.render_expr(e.value_data_type, cte=cte, cte_map=cte_map))}"
|
|
789
|
+
elif isinstance(e, list):
|
|
790
|
+
return f"{self.FUNCTION_MAP[FunctionType.ARRAY]([self.render_expr(x, cte=cte, cte_map=cte_map) for x in e])}"
|
|
789
791
|
elif isinstance(e, BuildParamaterizedConceptReference):
|
|
790
792
|
if self.rendering.parameters:
|
|
791
793
|
if e.concept.namespace == DEFAULT_NAMESPACE:
|
|
@@ -794,6 +796,7 @@ class BaseDialect:
|
|
|
794
796
|
elif e.concept.lineage:
|
|
795
797
|
return self.render_expr(e.concept.lineage, cte=cte, cte_map=cte_map)
|
|
796
798
|
return f"{self.QUOTE_CHARACTER}{e.concept.address}{self.QUOTE_CHARACTER}"
|
|
799
|
+
|
|
797
800
|
else:
|
|
798
801
|
raise ValueError(f"Unable to render type {type(e)} {e}")
|
|
799
802
|
|
trilogy/std/color.preql
ADDED
trilogy/std/display.preql
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
type percent float; # Percentage value
|
|
4
4
|
|
|
5
5
|
def calc_percent(a, b, digits=-1) -> case when digits =-1 then
|
|
6
|
-
case when b = 0 then 0.0::
|
|
7
|
-
(a/b)::
|
|
6
|
+
case when b = 0 then 0.0::numeric::percent else
|
|
7
|
+
(a/b)::numeric::percent end
|
|
8
8
|
else round((case when b = 0 then 0.0::float::percent else
|
|
9
|
-
(a/b)::float::percent end)::
|
|
9
|
+
(a/b)::float::percent end):: numeric::percent, digits) end;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|