pytrilogy 0.0.2.56__py3-none-any.whl → 0.0.2.57__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.56.dist-info → pytrilogy-0.0.2.57.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.2.56.dist-info → pytrilogy-0.0.2.57.dist-info}/RECORD +21 -21
- trilogy/__init__.py +1 -1
- trilogy/core/functions.py +2 -1
- trilogy/core/models.py +22 -14
- trilogy/core/processing/concept_strategies_v3.py +20 -14
- trilogy/core/processing/node_generators/basic_node.py +4 -1
- trilogy/core/processing/node_generators/group_to_node.py +10 -0
- trilogy/core/processing/node_generators/multiselect_node.py +16 -18
- trilogy/core/processing/node_generators/rowset_node.py +1 -0
- trilogy/core/processing/node_generators/select_merge_node.py +8 -1
- trilogy/core/processing/nodes/base_node.py +11 -0
- trilogy/core/processing/nodes/group_node.py +37 -40
- trilogy/core/processing/nodes/merge_node.py +1 -0
- trilogy/core/processing/utility.py +2 -3
- trilogy/dialect/base.py +3 -1
- trilogy/utility.py +5 -2
- {pytrilogy-0.0.2.56.dist-info → pytrilogy-0.0.2.57.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.56.dist-info → pytrilogy-0.0.2.57.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.2.56.dist-info → pytrilogy-0.0.2.57.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.2.56.dist-info → pytrilogy-0.0.2.57.dist-info}/top_level.txt +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
trilogy/__init__.py,sha256=
|
|
1
|
+
trilogy/__init__.py,sha256=YgiCOJgZfH7Ciz9GfXgtxr5_r5NkhAaRoFEru-cSoQs,291
|
|
2
2
|
trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
trilogy/constants.py,sha256=qZ1d0hoKPPV2HHCoFwPYTVB7b6bXjpWvXd3lE-zEhy8,1494
|
|
4
4
|
trilogy/engine.py,sha256=yOPnR7XCjWG82Gym_LLZBkYKKJdLCvqdCyt8zguNcnM,1103
|
|
5
5
|
trilogy/executor.py,sha256=SbReI_xWd081WZeRt_YAyVTdMOGg2XPrsaOKgMS7YUY,15969
|
|
6
6
|
trilogy/parser.py,sha256=UtuqSiGiCjpMAYgo1bvNq-b7NSzCA5hzbUW31RXaMII,281
|
|
7
7
|
trilogy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
trilogy/utility.py,sha256=
|
|
8
|
+
trilogy/utility.py,sha256=euQccZLKoYBz0LNg5tzLlvv2YHvXh9HArnYp1V3uXsM,763
|
|
9
9
|
trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
trilogy/core/constants.py,sha256=7XaCpZn5mQmjTobbeBn56SzPWq9eMNDfzfsRU-fP0VE,171
|
|
11
11
|
trilogy/core/enums.py,sha256=6pGjEXNJPB1ngbDQRJjxRi4NmKM8NZQ5-iwnZhrdo5U,7281
|
|
@@ -13,10 +13,10 @@ trilogy/core/env_processor.py,sha256=Pt4lmJfbShBbeSe5M7_FrTk5krrOziiAA__Slnettvc
|
|
|
13
13
|
trilogy/core/environment_helpers.py,sha256=ugKDnPYQNxKzc1Weq_kj9IVppNdgT8iS1RTS_f5hHxc,7905
|
|
14
14
|
trilogy/core/ergonomics.py,sha256=ASLDd0RqKWrZiG3XcKHo8nyTjaB_8xfE9t4NZ1UvGpc,1639
|
|
15
15
|
trilogy/core/exceptions.py,sha256=1c1lQCwSw4_5CQS3q7scOkXU8GQvullJXfPHubprl90,617
|
|
16
|
-
trilogy/core/functions.py,sha256=
|
|
16
|
+
trilogy/core/functions.py,sha256=8auZhInqnY28zg7Kil4LbvDT7jD4JggwS6HzK6ZIemE,10867
|
|
17
17
|
trilogy/core/graph_models.py,sha256=mameUTiuCajtihDw_2-W218xyJlvTusOWrEKP1yAWgk,2003
|
|
18
18
|
trilogy/core/internal.py,sha256=FQWbuETKPfzjALMmdXJwlOMlESfm2Z5gmErSsq3BX9c,1173
|
|
19
|
-
trilogy/core/models.py,sha256=
|
|
19
|
+
trilogy/core/models.py,sha256=FO1JUeUN8N3qqIjytLimayNLQWczq0aAYwephZyq7Ec,165389
|
|
20
20
|
trilogy/core/optimization.py,sha256=Jy3tVJNeqhpK6VSyTvgIWKCao6y-VCZ7mYA69MIF6L0,7989
|
|
21
21
|
trilogy/core/query_processor.py,sha256=JUtsDh64mWwQHM3HFZMPtVCu-Yw7WsK3cx4NxiMACSM,18584
|
|
22
22
|
trilogy/core/optimizations/__init__.py,sha256=EBanqTXEzf1ZEYjAneIWoIcxtMDite5-n2dQ5xcfUtg,356
|
|
@@ -25,19 +25,19 @@ trilogy/core/optimizations/inline_constant.py,sha256=c-YHOg6eAufL4EaCf4-0PbY_D4s
|
|
|
25
25
|
trilogy/core/optimizations/inline_datasource.py,sha256=LsngRKBy-LYcx1sfo1-rnDym_ly73YV9WkEngSjpFx8,3943
|
|
26
26
|
trilogy/core/optimizations/predicate_pushdown.py,sha256=XPWEBv8jXnc0OL2JDPNwFvJ5AtOE7dLzJK0LzdmdZMo,9252
|
|
27
27
|
trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
|
-
trilogy/core/processing/concept_strategies_v3.py,sha256=
|
|
28
|
+
trilogy/core/processing/concept_strategies_v3.py,sha256=hgxQ5nrlLYfx02yM--7GN8MoYYX06iLBFujjPoZaxGI,37683
|
|
29
29
|
trilogy/core/processing/graph_utils.py,sha256=stbYnDxnK-1kbo9L4XNU85FQhWCP-oZYO7LCXhAdC5M,1198
|
|
30
|
-
trilogy/core/processing/utility.py,sha256=
|
|
30
|
+
trilogy/core/processing/utility.py,sha256=JpyPScfD8i4CgsTZSR0siWdXVhQDHizBAiQc81d7lbw,19769
|
|
31
31
|
trilogy/core/processing/node_generators/__init__.py,sha256=s_YV1OYc336DuS9591259qjI_K_CtOCuhkf4t2aOgYs,733
|
|
32
|
-
trilogy/core/processing/node_generators/basic_node.py,sha256=
|
|
32
|
+
trilogy/core/processing/node_generators/basic_node.py,sha256=pExVmLDQK9okXNeC1-jQgDwpj8JWAgQfejd2lMt8L4U,3157
|
|
33
33
|
trilogy/core/processing/node_generators/common.py,sha256=dHycWu9iiRxH3WIkkyibsnYD5mJfXvdEOhsTvyaO8fg,9128
|
|
34
34
|
trilogy/core/processing/node_generators/filter_node.py,sha256=aWR82yAZOAnUrJejTj6yD4jpqH6cSPzyJMd1V-M0Kj0,7883
|
|
35
35
|
trilogy/core/processing/node_generators/group_node.py,sha256=k57SVWHSVvTqCd47tyLUGCsSZaP7UQqMCJYTSz1S7oQ,5566
|
|
36
|
-
trilogy/core/processing/node_generators/group_to_node.py,sha256=
|
|
37
|
-
trilogy/core/processing/node_generators/multiselect_node.py,sha256=
|
|
36
|
+
trilogy/core/processing/node_generators/group_to_node.py,sha256=Hz17vZ1EjKVa275CZPF12FkLdrc916PA5T6OsfgryRQ,2928
|
|
37
|
+
trilogy/core/processing/node_generators/multiselect_node.py,sha256=9bQPla367WT85iWGZxlZM4EWkWFGM0i6jgBuA4O0QvQ,6464
|
|
38
38
|
trilogy/core/processing/node_generators/node_merge_node.py,sha256=3GzuiTiorFVe9MyLhoz2PDyI0x9XL7bQ8ucEbV54le8,14627
|
|
39
|
-
trilogy/core/processing/node_generators/rowset_node.py,sha256=
|
|
40
|
-
trilogy/core/processing/node_generators/select_merge_node.py,sha256=
|
|
39
|
+
trilogy/core/processing/node_generators/rowset_node.py,sha256=aSk1Ltv1S6aSRKHpWGjEjgrNTbJXuIXkFiGQVZOyb1o,5139
|
|
40
|
+
trilogy/core/processing/node_generators/select_merge_node.py,sha256=yfNeuc24Ejn7j07szwJif60qmU6OVk3wfa7C2_RJ39k,15996
|
|
41
41
|
trilogy/core/processing/node_generators/select_node.py,sha256=bjTylBa-vYbmzpuSpphmIo_Oi78YZpI8ppHnN9KDYDk,1795
|
|
42
42
|
trilogy/core/processing/node_generators/union_node.py,sha256=MfJjF2m0ARl0oUH9QT1awzPv0e3yA3mXK1XqAvUTgKw,2504
|
|
43
43
|
trilogy/core/processing/node_generators/unnest_node.py,sha256=8El2B1mzC9vIUSk-m94xHvaJwAf5GtCAGfTxGDSiqmU,2229
|
|
@@ -45,16 +45,16 @@ trilogy/core/processing/node_generators/window_node.py,sha256=5htRRxaxw6EnS-2TVo
|
|
|
45
45
|
trilogy/core/processing/node_generators/select_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
46
|
trilogy/core/processing/node_generators/select_helpers/datasource_injection.py,sha256=hJZS7GT0dl7sK0riweEwVAVRv5LCXOnMD1hF0XY9hpE,6548
|
|
47
47
|
trilogy/core/processing/nodes/__init__.py,sha256=WNUmYmZF3uqF2qiJ1L7y0u9qiVD9YnluKds0wA5opJE,4813
|
|
48
|
-
trilogy/core/processing/nodes/base_node.py,sha256=
|
|
48
|
+
trilogy/core/processing/nodes/base_node.py,sha256=utIs_c5V7SIRDPrIVlHJ7zD4caRFmffhuJQSM4cVYoY,16104
|
|
49
49
|
trilogy/core/processing/nodes/filter_node.py,sha256=j7icDAXJ7oFPkHTOQVmm9QbZxrhhYEUGJj2lSiguXKA,2292
|
|
50
|
-
trilogy/core/processing/nodes/group_node.py,sha256
|
|
51
|
-
trilogy/core/processing/nodes/merge_node.py,sha256=
|
|
50
|
+
trilogy/core/processing/nodes/group_node.py,sha256=-dx_g1b6j3zygLKWp8yPYtnFxwLtKT9wHv62-U7GBZQ,7273
|
|
51
|
+
trilogy/core/processing/nodes/merge_node.py,sha256=kU4JChblGEoule-qKyXAwlQ2UtLXZsvugL50iUVQvQQ,14760
|
|
52
52
|
trilogy/core/processing/nodes/select_node_v2.py,sha256=t3ln9Kxeml8mVTnLgtNPvavb5TLTRtfkJ0nyxh7UYUs,8212
|
|
53
53
|
trilogy/core/processing/nodes/union_node.py,sha256=1QgOWkjJ-ADFdanoRzi0EM5buhuzJbmlda9BAUGp4mM,1352
|
|
54
54
|
trilogy/core/processing/nodes/unnest_node.py,sha256=0TFANwqVPaVpUR6SF5uweGTlXfEnagXRBBZU6dUwtcY,2101
|
|
55
55
|
trilogy/core/processing/nodes/window_node.py,sha256=yYwWuOq1Uwm-xEl8lFH_urm-YXaAGAgNhE20MEoD5QQ,1163
|
|
56
56
|
trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
57
|
-
trilogy/dialect/base.py,sha256=
|
|
57
|
+
trilogy/dialect/base.py,sha256=jRHr_LrI0M7Pak3HizuBcbeTnAJ2e0NoYLMpGHXJhUw,38590
|
|
58
58
|
trilogy/dialect/bigquery.py,sha256=mKC3zoEU232h9RtIXJjqiZ72lWH8a6S28p6wAZKrAfg,2952
|
|
59
59
|
trilogy/dialect/common.py,sha256=b0E6JqdKaaSzThLiFa9jwUg4YnXahf-3bqmzOn5z-6E,3827
|
|
60
60
|
trilogy/dialect/config.py,sha256=UiBY2tBbNk9owx-zxP_3lN9lErEUXhXIU_bcXA18AvU,2992
|
|
@@ -79,9 +79,9 @@ trilogy/parsing/render.py,sha256=o4C12a407iZvlRGUJDiuJUezrLLo4QEaLtu60ZQX3gk,169
|
|
|
79
79
|
trilogy/parsing/trilogy.lark,sha256=EazfEvYPuvkPkNjUnVzFi0uD9baavugbSI8CyfawShk,12573
|
|
80
80
|
trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
81
81
|
trilogy/scripts/trilogy.py,sha256=DQDW81E5mDMWFP8oPw8q-IyrR2JGxQSDWgUWe2VTSRQ,3731
|
|
82
|
-
pytrilogy-0.0.2.
|
|
83
|
-
pytrilogy-0.0.2.
|
|
84
|
-
pytrilogy-0.0.2.
|
|
85
|
-
pytrilogy-0.0.2.
|
|
86
|
-
pytrilogy-0.0.2.
|
|
87
|
-
pytrilogy-0.0.2.
|
|
82
|
+
pytrilogy-0.0.2.57.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
83
|
+
pytrilogy-0.0.2.57.dist-info/METADATA,sha256=--PBPmro81sFziRia1QJYSp1zQBGRmEUo46DYa-xBgg,8823
|
|
84
|
+
pytrilogy-0.0.2.57.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
85
|
+
pytrilogy-0.0.2.57.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
|
|
86
|
+
pytrilogy-0.0.2.57.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
87
|
+
pytrilogy-0.0.2.57.dist-info/RECORD,,
|
trilogy/__init__.py
CHANGED
trilogy/core/functions.py
CHANGED
|
@@ -127,10 +127,11 @@ def Unnest(args: list[Concept]) -> Function:
|
|
|
127
127
|
|
|
128
128
|
def Group(args: list[Concept]) -> Function:
|
|
129
129
|
output = args[0]
|
|
130
|
+
datatype = arg_to_datatype(output)
|
|
130
131
|
return Function(
|
|
131
132
|
operator=FunctionType.GROUP,
|
|
132
133
|
arguments=args,
|
|
133
|
-
output_datatype=
|
|
134
|
+
output_datatype=datatype,
|
|
134
135
|
output_purpose=Purpose.PROPERTY,
|
|
135
136
|
arg_count=-1,
|
|
136
137
|
)
|
trilogy/core/models.py
CHANGED
|
@@ -923,9 +923,16 @@ class Grain(Namespaced, BaseModel):
|
|
|
923
923
|
if not self.where_clause:
|
|
924
924
|
where = other.where_clause
|
|
925
925
|
elif not other.where_clause == self.where_clause:
|
|
926
|
-
|
|
927
|
-
|
|
926
|
+
where = WhereClause(
|
|
927
|
+
conditional=Conditional(
|
|
928
|
+
left=self.where_clause.conditional,
|
|
929
|
+
right=other.where_clause.conditional,
|
|
930
|
+
operator=BooleanOperator.AND,
|
|
931
|
+
)
|
|
928
932
|
)
|
|
933
|
+
# raise NotImplementedError(
|
|
934
|
+
# f"Cannot merge grains with where clauses, self {self.where_clause} other {other.where_clause}"
|
|
935
|
+
# )
|
|
929
936
|
return Grain(
|
|
930
937
|
components=self.components.union(other.components), where_clause=where
|
|
931
938
|
)
|
|
@@ -2182,6 +2189,10 @@ class Datasource(HasUUID, Namespaced, BaseModel):
|
|
|
2182
2189
|
def duplicate(self) -> Datasource:
|
|
2183
2190
|
return self.model_copy(deep=True)
|
|
2184
2191
|
|
|
2192
|
+
@property
|
|
2193
|
+
def hidden_concepts(self) -> List[Concept]:
|
|
2194
|
+
return []
|
|
2195
|
+
|
|
2185
2196
|
def merge_concept(
|
|
2186
2197
|
self, source: Concept, target: Concept, modifiers: List[Modifier]
|
|
2187
2198
|
):
|
|
@@ -2254,17 +2265,7 @@ class Datasource(HasUUID, Namespaced, BaseModel):
|
|
|
2254
2265
|
@field_validator("grain", mode="before")
|
|
2255
2266
|
@classmethod
|
|
2256
2267
|
def grain_enforcement(cls, v: Grain, info: ValidationInfo):
|
|
2257
|
-
values = info.data
|
|
2258
2268
|
grain: Grain = safe_grain(v)
|
|
2259
|
-
if not grain.components:
|
|
2260
|
-
columns: List[ColumnAssignment] = values.get("columns", [])
|
|
2261
|
-
grain = Grain.from_concepts(
|
|
2262
|
-
[
|
|
2263
|
-
c.concept.with_grain(Grain())
|
|
2264
|
-
for c in columns
|
|
2265
|
-
if c.concept.purpose == Purpose.KEY
|
|
2266
|
-
]
|
|
2267
|
-
)
|
|
2268
2269
|
return grain
|
|
2269
2270
|
|
|
2270
2271
|
def add_column(
|
|
@@ -3073,12 +3074,18 @@ class CTE(BaseModel):
|
|
|
3073
3074
|
assert isinstance(c.lineage, RowsetItem)
|
|
3074
3075
|
return check_is_not_in_group(c.lineage.content)
|
|
3075
3076
|
if c.derivation == PurposeLineage.CONSTANT:
|
|
3076
|
-
return
|
|
3077
|
+
return True
|
|
3077
3078
|
if c.purpose == Purpose.METRIC:
|
|
3078
3079
|
return True
|
|
3079
|
-
|
|
3080
|
+
|
|
3081
|
+
if c.derivation == PurposeLineage.BASIC and c.lineage:
|
|
3080
3082
|
if all([check_is_not_in_group(x) for x in c.lineage.concept_arguments]):
|
|
3081
3083
|
return True
|
|
3084
|
+
if (
|
|
3085
|
+
isinstance(c.lineage, Function)
|
|
3086
|
+
and c.lineage.operator == FunctionType.GROUP
|
|
3087
|
+
):
|
|
3088
|
+
return check_is_not_in_group(c.lineage.concept_arguments[0])
|
|
3082
3089
|
return False
|
|
3083
3090
|
|
|
3084
3091
|
return (
|
|
@@ -3756,6 +3763,7 @@ class Environment(BaseModel):
|
|
|
3756
3763
|
for k, v in self.concepts.items():
|
|
3757
3764
|
if v.address == target.address:
|
|
3758
3765
|
v.pseudonyms.add(source.address)
|
|
3766
|
+
|
|
3759
3767
|
if v.address == source.address:
|
|
3760
3768
|
replacements[k] = target
|
|
3761
3769
|
v.pseudonyms.add(target.address)
|
|
@@ -539,11 +539,14 @@ def validate_concept(
|
|
|
539
539
|
found_addresses.add(concept.address)
|
|
540
540
|
found_map[str(node)].add(concept)
|
|
541
541
|
for v_address in concept.pseudonyms:
|
|
542
|
+
if v_address in seen:
|
|
543
|
+
return
|
|
542
544
|
v = environment.concepts[v_address]
|
|
543
|
-
if v
|
|
545
|
+
if v.address in seen:
|
|
544
546
|
return
|
|
545
|
-
if v
|
|
547
|
+
if v.address == concept.address:
|
|
546
548
|
return
|
|
549
|
+
|
|
547
550
|
validate_concept(
|
|
548
551
|
v,
|
|
549
552
|
node,
|
|
@@ -988,17 +991,20 @@ def source_query_concepts(
|
|
|
988
991
|
raise ValueError(
|
|
989
992
|
f"Could not resolve conections between {error_strings} from environment graph."
|
|
990
993
|
)
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
],
|
|
995
|
-
input_concepts=[
|
|
996
|
-
x for x in root.output_concepts if x.address not in root.hidden_concepts
|
|
997
|
-
],
|
|
994
|
+
final = [x for x in root.output_concepts if x.address not in root.hidden_concepts]
|
|
995
|
+
if GroupNode.check_if_required(
|
|
996
|
+
downstream_concepts=final,
|
|
997
|
+
parents=[root.resolve()],
|
|
998
998
|
environment=environment,
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
999
|
+
).required:
|
|
1000
|
+
candidate: StrategyNode = GroupNode(
|
|
1001
|
+
output_concepts=final,
|
|
1002
|
+
input_concepts=final,
|
|
1003
|
+
environment=environment,
|
|
1004
|
+
parents=[root],
|
|
1005
|
+
partial_concepts=root.partial_concepts,
|
|
1006
|
+
)
|
|
1007
|
+
else:
|
|
1008
|
+
candidate = root
|
|
1009
|
+
|
|
1004
1010
|
return candidate
|
|
@@ -13,6 +13,7 @@ from trilogy.core.processing.node_generators.common import (
|
|
|
13
13
|
resolve_function_parent_concepts,
|
|
14
14
|
)
|
|
15
15
|
from trilogy.core.processing.nodes import History, StrategyNode
|
|
16
|
+
from trilogy.utility import unique
|
|
16
17
|
|
|
17
18
|
LOGGER_PREFIX = "[GEN_BASIC_NODE]"
|
|
18
19
|
|
|
@@ -65,7 +66,9 @@ def gen_basic_node(
|
|
|
65
66
|
non_equivalent_optional = [
|
|
66
67
|
x for x in local_optional if x not in equivalent_optional
|
|
67
68
|
]
|
|
68
|
-
all_parents =
|
|
69
|
+
all_parents: list[Concept] = unique(
|
|
70
|
+
parent_concepts + non_equivalent_optional, "address"
|
|
71
|
+
)
|
|
69
72
|
logger.info(
|
|
70
73
|
f"{depth_prefix}{LOGGER_PREFIX} Fetching parents {[x.address for x in all_parents]}"
|
|
71
74
|
)
|
|
@@ -26,6 +26,7 @@ def gen_group_to_node(
|
|
|
26
26
|
# aggregates MUST always group to the proper grain
|
|
27
27
|
if not isinstance(concept.lineage, Function):
|
|
28
28
|
raise SyntaxError("Group to should have function lineage")
|
|
29
|
+
group_arg = concept.lineage.arguments[0]
|
|
29
30
|
parent_concepts: List[Concept] = concept.lineage.concept_arguments
|
|
30
31
|
logger.info(
|
|
31
32
|
f"{padding(depth)}{LOGGER_PREFIX} group by node has required parents {[x.address for x in parent_concepts]}"
|
|
@@ -47,6 +48,13 @@ def gen_group_to_node(
|
|
|
47
48
|
environment=environment,
|
|
48
49
|
parents=parents,
|
|
49
50
|
depth=depth,
|
|
51
|
+
preexisting_conditions=conditions.conditional if conditions else None,
|
|
52
|
+
hidden_concepts=(
|
|
53
|
+
[group_arg]
|
|
54
|
+
if isinstance(group_arg, Concept)
|
|
55
|
+
and group_arg.address not in local_optional
|
|
56
|
+
else []
|
|
57
|
+
),
|
|
50
58
|
)
|
|
51
59
|
|
|
52
60
|
# early exit if no optional
|
|
@@ -62,6 +70,7 @@ def gen_group_to_node(
|
|
|
62
70
|
g=g,
|
|
63
71
|
depth=depth + 1,
|
|
64
72
|
history=history,
|
|
73
|
+
conditions=conditions,
|
|
65
74
|
)
|
|
66
75
|
if not enrich_node:
|
|
67
76
|
logger.info(
|
|
@@ -83,4 +92,5 @@ def gen_group_to_node(
|
|
|
83
92
|
],
|
|
84
93
|
whole_grain=True,
|
|
85
94
|
depth=depth,
|
|
95
|
+
preexisting_conditions=conditions.conditional if conditions else None,
|
|
86
96
|
)
|
|
@@ -69,6 +69,7 @@ def gen_multiselect_node(
|
|
|
69
69
|
lineage: MultiSelectStatement = concept.lineage
|
|
70
70
|
|
|
71
71
|
base_parents: List[StrategyNode] = []
|
|
72
|
+
partial = []
|
|
72
73
|
for select in lineage.selects:
|
|
73
74
|
snode: StrategyNode = source_concepts(
|
|
74
75
|
mandatory_list=select.output_components,
|
|
@@ -103,6 +104,9 @@ def gen_multiselect_node(
|
|
|
103
104
|
for mc in merge_concepts:
|
|
104
105
|
assert mc in snode.resolve().output_concepts
|
|
105
106
|
base_parents.append(snode)
|
|
107
|
+
if select.where_clause:
|
|
108
|
+
for item in select.output_components:
|
|
109
|
+
partial.append(item)
|
|
106
110
|
|
|
107
111
|
node_joins = extra_align_joins(lineage, base_parents)
|
|
108
112
|
node = MergeNode(
|
|
@@ -112,35 +116,28 @@ def gen_multiselect_node(
|
|
|
112
116
|
depth=depth,
|
|
113
117
|
parents=base_parents,
|
|
114
118
|
node_joins=node_joins,
|
|
119
|
+
hidden_concepts=[x for y in base_parents for x in y.hidden_concepts],
|
|
115
120
|
)
|
|
116
121
|
|
|
117
122
|
enrichment = set([x.address for x in local_optional])
|
|
118
123
|
|
|
119
|
-
|
|
124
|
+
multiselect_relevant = [
|
|
120
125
|
x
|
|
121
126
|
for x in lineage.derived_concepts
|
|
122
127
|
if x.address == concept.address or x.address in enrichment
|
|
123
128
|
]
|
|
124
|
-
additional_relevant = [
|
|
125
|
-
x for x in select.output_components if x.address in enrichment
|
|
126
|
-
]
|
|
129
|
+
additional_relevant = [x for x in node.output_concepts if x.address in enrichment]
|
|
127
130
|
# add in other other concepts
|
|
128
|
-
for item in rowset_relevant:
|
|
129
|
-
node.output_concepts.append(item)
|
|
130
|
-
for item in additional_relevant:
|
|
131
|
-
node.output_concepts.append(item)
|
|
132
|
-
if select.where_clause:
|
|
133
|
-
for item in additional_relevant:
|
|
134
|
-
node.partial_concepts.append(item)
|
|
135
131
|
|
|
136
|
-
|
|
137
|
-
node.resolution_cache = node._resolve()
|
|
132
|
+
node.set_output_concepts(multiselect_relevant + additional_relevant)
|
|
138
133
|
|
|
139
|
-
#
|
|
140
|
-
#
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
)
|
|
134
|
+
# node.add_partial_concepts(partial)
|
|
135
|
+
# if select.where_clause:
|
|
136
|
+
# for item in additional_relevant:
|
|
137
|
+
# node.partial_concepts.append(item)
|
|
138
|
+
node.grain = Grain.from_concepts(node.output_concepts, environment=environment)
|
|
139
|
+
node.rebuild_cache()
|
|
140
|
+
# we need a better API for refreshing a nodes QDS
|
|
144
141
|
possible_joins = concept_to_relevant_joins(additional_relevant)
|
|
145
142
|
if not local_optional:
|
|
146
143
|
logger.info(
|
|
@@ -159,6 +156,7 @@ def gen_multiselect_node(
|
|
|
159
156
|
f"{padding(depth)}{LOGGER_PREFIX} all enriched concepts returned from base rowset node; exiting early"
|
|
160
157
|
)
|
|
161
158
|
return node
|
|
159
|
+
|
|
162
160
|
enrich_node: MergeNode = source_concepts( # this fetches the parent + join keys
|
|
163
161
|
# to then connect to the rest of the query
|
|
164
162
|
mandatory_list=additional_relevant + local_optional,
|
|
@@ -240,10 +240,12 @@ def create_datasource_node(
|
|
|
240
240
|
depth: int,
|
|
241
241
|
conditions: WhereClause | None = None,
|
|
242
242
|
) -> tuple[StrategyNode, bool]:
|
|
243
|
-
target_grain = Grain.from_concepts(all_concepts)
|
|
243
|
+
target_grain = Grain.from_concepts(all_concepts, environment=environment)
|
|
244
244
|
force_group = False
|
|
245
245
|
if not datasource.grain.issubset(target_grain):
|
|
246
246
|
force_group = True
|
|
247
|
+
if not datasource.grain.components:
|
|
248
|
+
force_group = True
|
|
247
249
|
partial_concepts = [
|
|
248
250
|
c.concept
|
|
249
251
|
for c in datasource.columns
|
|
@@ -350,6 +352,9 @@ def create_select_node(
|
|
|
350
352
|
|
|
351
353
|
# we need to nest the group node one further
|
|
352
354
|
if force_group is True:
|
|
355
|
+
logger.info(
|
|
356
|
+
f"{padding(depth)}{LOGGER_PREFIX} source requires group before consumption."
|
|
357
|
+
)
|
|
353
358
|
candidate: StrategyNode = GroupNode(
|
|
354
359
|
output_concepts=all_concepts,
|
|
355
360
|
input_concepts=all_concepts,
|
|
@@ -359,8 +364,10 @@ def create_select_node(
|
|
|
359
364
|
partial_concepts=bcandidate.partial_concepts,
|
|
360
365
|
nullable_concepts=bcandidate.nullable_concepts,
|
|
361
366
|
preexisting_conditions=bcandidate.preexisting_conditions,
|
|
367
|
+
force_group=force_group,
|
|
362
368
|
)
|
|
363
369
|
else:
|
|
370
|
+
|
|
364
371
|
candidate = bcandidate
|
|
365
372
|
return candidate
|
|
366
373
|
|
|
@@ -192,6 +192,8 @@ class StrategyNode:
|
|
|
192
192
|
for x in self.parents:
|
|
193
193
|
for z in x.usable_outputs:
|
|
194
194
|
non_hidden.add(z.address)
|
|
195
|
+
for psd in z.pseudonyms:
|
|
196
|
+
non_hidden.add(psd)
|
|
195
197
|
if not all([x.address in non_hidden for x in self.input_concepts]):
|
|
196
198
|
missing = [x for x in self.input_concepts if x.address not in non_hidden]
|
|
197
199
|
raise ValueError(
|
|
@@ -246,6 +248,15 @@ class StrategyNode:
|
|
|
246
248
|
self.rebuild_cache()
|
|
247
249
|
return self
|
|
248
250
|
|
|
251
|
+
def add_partial_concepts(self, concepts: List[Concept], rebuild: bool = True):
|
|
252
|
+
for concept in concepts:
|
|
253
|
+
if concept.address not in self.partial_lcl.addresses:
|
|
254
|
+
self.partial_concepts.append(concept)
|
|
255
|
+
self.partial_lcl = LooseConceptList(concepts=self.partial_concepts)
|
|
256
|
+
if rebuild:
|
|
257
|
+
self.rebuild_cache()
|
|
258
|
+
return self
|
|
259
|
+
|
|
249
260
|
def add_existence_concepts(self, concepts: List[Concept], rebuild: bool = True):
|
|
250
261
|
for concept in concepts:
|
|
251
262
|
if concept.address not in self.output_concepts:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
1
2
|
from typing import List, Optional
|
|
2
3
|
|
|
3
4
|
from trilogy.constants import logger
|
|
@@ -8,7 +9,6 @@ from trilogy.core.models import (
|
|
|
8
9
|
Datasource,
|
|
9
10
|
Environment,
|
|
10
11
|
Grain,
|
|
11
|
-
LooseConceptList,
|
|
12
12
|
Parenthetical,
|
|
13
13
|
QueryDatasource,
|
|
14
14
|
SourceType,
|
|
@@ -24,6 +24,13 @@ from trilogy.utility import unique
|
|
|
24
24
|
LOGGER_PREFIX = "[CONCEPT DETAIL - GROUP NODE]"
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
@dataclass
|
|
28
|
+
class GroupRequiredResponse:
|
|
29
|
+
target: Grain
|
|
30
|
+
upstream: Grain
|
|
31
|
+
required: bool
|
|
32
|
+
|
|
33
|
+
|
|
27
34
|
class GroupNode(StrategyNode):
|
|
28
35
|
source_type = SourceType.GROUP
|
|
29
36
|
|
|
@@ -59,45 +66,44 @@ class GroupNode(StrategyNode):
|
|
|
59
66
|
hidden_concepts=hidden_concepts,
|
|
60
67
|
)
|
|
61
68
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
]
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
@classmethod
|
|
70
|
+
def check_if_required(
|
|
71
|
+
cls,
|
|
72
|
+
downstream_concepts: List[Concept],
|
|
73
|
+
parents: list[QueryDatasource | Datasource],
|
|
74
|
+
environment: Environment,
|
|
75
|
+
) -> GroupRequiredResponse:
|
|
76
|
+
target_grain = Grain.from_concepts(
|
|
68
77
|
concepts_to_grain_concepts(
|
|
69
|
-
|
|
78
|
+
downstream_concepts,
|
|
79
|
+
environment=environment,
|
|
70
80
|
)
|
|
71
81
|
)
|
|
72
82
|
comp_grain = Grain()
|
|
73
|
-
for source in
|
|
83
|
+
for source in parents:
|
|
74
84
|
comp_grain += source.grain
|
|
75
85
|
comp_grain = Grain.from_concepts(
|
|
76
|
-
concepts_to_grain_concepts(
|
|
77
|
-
comp_grain.components, environment=self.environment
|
|
78
|
-
)
|
|
86
|
+
concepts_to_grain_concepts(comp_grain.components, environment=environment)
|
|
79
87
|
)
|
|
80
88
|
# dynamically select if we need to group
|
|
81
89
|
# because sometimes, we are already at required grain
|
|
82
|
-
if comp_grain
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
will_return.condition = self.conditions + will_return.condition
|
|
100
|
-
return will_return
|
|
90
|
+
if comp_grain.issubset(target_grain):
|
|
91
|
+
return GroupRequiredResponse(target_grain, comp_grain, False)
|
|
92
|
+
|
|
93
|
+
return GroupRequiredResponse(target_grain, comp_grain, True)
|
|
94
|
+
|
|
95
|
+
def _resolve(self) -> QueryDatasource:
|
|
96
|
+
parent_sources: List[QueryDatasource | Datasource] = [
|
|
97
|
+
p.resolve() for p in self.parents
|
|
98
|
+
]
|
|
99
|
+
grains = self.check_if_required(
|
|
100
|
+
self.output_concepts, parent_sources, self.environment
|
|
101
|
+
)
|
|
102
|
+
target_grain = grains.target
|
|
103
|
+
comp_grain = grains.upstream
|
|
104
|
+
# dynamically select if we need to group
|
|
105
|
+
# because sometimes, we are already at required grain
|
|
106
|
+
if not grains.required and self.force_group is not True:
|
|
101
107
|
# otherwise if no group by, just treat it as a select
|
|
102
108
|
source_type = SourceType.SELECT
|
|
103
109
|
else:
|
|
@@ -108,15 +114,6 @@ class GroupNode(StrategyNode):
|
|
|
108
114
|
f" target grain {target_grain}"
|
|
109
115
|
f" delta: {comp_grain - target_grain}"
|
|
110
116
|
)
|
|
111
|
-
for parent in self.parents:
|
|
112
|
-
logger.info(
|
|
113
|
-
f"{self.logging_prefix}{LOGGER_PREFIX} Parent node"
|
|
114
|
-
f" {[c.address for c in parent.output_concepts[:2]]}... has"
|
|
115
|
-
" set node grain"
|
|
116
|
-
f" {parent.grain}"
|
|
117
|
-
f" and resolved grain {parent.resolve().grain}"
|
|
118
|
-
f" {type(parent)}"
|
|
119
|
-
)
|
|
120
117
|
source_type = SourceType.GROUP
|
|
121
118
|
source_map = resolve_concept_map(
|
|
122
119
|
parent_sources,
|
|
@@ -310,6 +310,7 @@ class MergeNode(StrategyNode):
|
|
|
310
310
|
for join in joins:
|
|
311
311
|
if isinstance(join, BaseJoin) and join.join_type == JoinType.FULL:
|
|
312
312
|
full_join_concepts += join.input_concepts
|
|
313
|
+
|
|
313
314
|
if self.whole_grain:
|
|
314
315
|
force_group = False
|
|
315
316
|
elif self.force_group is False:
|
|
@@ -6,7 +6,6 @@ from typing import Any, Dict, List, Set, Tuple
|
|
|
6
6
|
|
|
7
7
|
import networkx as nx
|
|
8
8
|
|
|
9
|
-
from trilogy.constants import logger
|
|
10
9
|
from trilogy.core.enums import BooleanOperator, FunctionClass, Granularity, Purpose
|
|
11
10
|
from trilogy.core.models import (
|
|
12
11
|
CTE,
|
|
@@ -161,8 +160,6 @@ def resolve_join_order_v2(
|
|
|
161
160
|
final_join_type = JoinType.LEFT_OUTER
|
|
162
161
|
elif any([x == JoinType.FULL for x in join_types]):
|
|
163
162
|
final_join_type = JoinType.FULL
|
|
164
|
-
logger.info("JOIN DEBUG")
|
|
165
|
-
logger.info(joinkeys)
|
|
166
163
|
output.append(
|
|
167
164
|
JoinOrderOutput(
|
|
168
165
|
# left=left_candidate,
|
|
@@ -350,6 +347,8 @@ def get_node_joins(
|
|
|
350
347
|
graph.add_node(ds_node, type=NodeType.NODE)
|
|
351
348
|
partials[ds_node] = [f"c~{c.address}" for c in datasource.partial_concepts]
|
|
352
349
|
for concept in datasource.output_concepts:
|
|
350
|
+
if concept in datasource.hidden_concepts:
|
|
351
|
+
continue
|
|
353
352
|
add_node_join_concept(
|
|
354
353
|
graph=graph,
|
|
355
354
|
concept=concept,
|
trilogy/dialect/base.py
CHANGED
|
@@ -249,7 +249,9 @@ def safe_get_cte_value(coalesce, cte: CTE | UnionCTE, c: Concept, quote_char: st
|
|
|
249
249
|
if isinstance(raw, list) and len(raw) == 1:
|
|
250
250
|
rendered = cte.get_alias(c, raw[0])
|
|
251
251
|
return f"{raw[0]}.{safe_quote(rendered, quote_char)}"
|
|
252
|
-
return coalesce(
|
|
252
|
+
return coalesce(
|
|
253
|
+
sorted([f"{x}.{safe_quote(cte.get_alias(c, x), quote_char)}" for x in raw])
|
|
254
|
+
)
|
|
253
255
|
|
|
254
256
|
|
|
255
257
|
class BaseDialect:
|
trilogy/utility.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import hashlib
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Callable, List, TypeVar, Union
|
|
3
3
|
|
|
4
4
|
from trilogy.constants import DEFAULT_NAMESPACE
|
|
5
5
|
|
|
@@ -12,7 +12,10 @@ def string_to_hash(input: str) -> int:
|
|
|
12
12
|
)
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
UniqueArg = TypeVar("UniqueArg")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def unique(inputs: List[UniqueArg], property: Union[str, Callable]) -> List[UniqueArg]:
|
|
16
19
|
final = []
|
|
17
20
|
dedupe = set()
|
|
18
21
|
if isinstance(property, str):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|