pytrilogy 0.0.2.56__py3-none-any.whl → 0.0.2.58__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.58.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.2.56.dist-info → pytrilogy-0.0.2.58.dist-info}/RECORD +25 -25
- trilogy/__init__.py +1 -1
- trilogy/core/functions.py +2 -1
- trilogy/core/models.py +38 -30
- trilogy/core/optimization.py +3 -6
- trilogy/core/processing/concept_strategies_v3.py +21 -15
- trilogy/core/processing/node_generators/basic_node.py +4 -1
- trilogy/core/processing/node_generators/common.py +1 -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 +5 -2
- trilogy/core/processing/node_generators/select_merge_node.py +8 -1
- trilogy/core/processing/nodes/base_node.py +21 -11
- trilogy/core/processing/nodes/group_node.py +42 -45
- trilogy/core/processing/nodes/merge_node.py +3 -2
- trilogy/core/processing/nodes/select_node_v2.py +1 -1
- trilogy/core/processing/utility.py +6 -7
- trilogy/core/query_processor.py +2 -2
- trilogy/dialect/base.py +9 -6
- trilogy/utility.py +5 -2
- {pytrilogy-0.0.2.56.dist-info → pytrilogy-0.0.2.58.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.56.dist-info → pytrilogy-0.0.2.58.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.2.56.dist-info → pytrilogy-0.0.2.58.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.2.56.dist-info → pytrilogy-0.0.2.58.dist-info}/top_level.txt +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
trilogy/__init__.py,sha256=
|
|
1
|
+
trilogy/__init__.py,sha256=CTKkLw46Arh2GYkTbonpNlj9mv7xV7TLDpB2rTn_afU,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,31 +13,31 @@ 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=
|
|
20
|
-
trilogy/core/optimization.py,sha256=
|
|
21
|
-
trilogy/core/query_processor.py,sha256
|
|
19
|
+
trilogy/core/models.py,sha256=VrbgVuyKPtMwt3wpQnFFYFpzqNX3B94rlw6PxvpazNQ,165392
|
|
20
|
+
trilogy/core/optimization.py,sha256=dIgpcletzEtcX0hAq74Y4cleAWkznR422geo00rZFKk,7900
|
|
21
|
+
trilogy/core/query_processor.py,sha256=-KjOKsRhvpdR_NKxoyLq83T97Aw3J-4De-hpslzhKfQ,18576
|
|
22
22
|
trilogy/core/optimizations/__init__.py,sha256=EBanqTXEzf1ZEYjAneIWoIcxtMDite5-n2dQ5xcfUtg,356
|
|
23
23
|
trilogy/core/optimizations/base_optimization.py,sha256=P4kF-eCXkBxO-5c6tLHhMZ4ODRH1A04hb_6ovkaVyLw,505
|
|
24
24
|
trilogy/core/optimizations/inline_constant.py,sha256=c-YHOg6eAufL4EaCf4-0PbY_D4skBHW0ldR55_phsMA,1277
|
|
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=Ty8JfK0KSCW-RBvw4v7RERe7xuSbzJlRSaqRfcSfOBE,37691
|
|
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=N2RUGtUuktvy1VXoVpve8F2QtCBEFQf6RWpr0PXXhwk,19671
|
|
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=
|
|
33
|
-
trilogy/core/processing/node_generators/common.py,sha256=
|
|
32
|
+
trilogy/core/processing/node_generators/basic_node.py,sha256=pExVmLDQK9okXNeC1-jQgDwpj8JWAgQfejd2lMt8L4U,3157
|
|
33
|
+
trilogy/core/processing/node_generators/common.py,sha256=Ft5giLHFn0t_A_4FPl6MJWsi0ghEen0bPFAmrdZ3S58,9107
|
|
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=CZiPnD4rEkMOdyBQrDaH6HA3VSMKJ775BiJdQkM7dO8,2939
|
|
37
|
+
trilogy/core/processing/node_generators/multiselect_node.py,sha256=qmaufoSVwavNBNsdw0NGpNOTNWFPDKunA9COohEYW08,6469
|
|
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=FDCJORLEcmpF9a071IFl-VbmKjq32rFOXi1SarjBVxY,5167
|
|
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=YgZaiAlzvwa7AkHXNfMo5R0DJKWdvbDvMWEvTJR7fpM,16081
|
|
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=
|
|
52
|
-
trilogy/core/processing/nodes/select_node_v2.py,sha256=
|
|
50
|
+
trilogy/core/processing/nodes/group_node.py,sha256=7lX9SvOK_-oPeg4UydjYMcp4BB7IneJxT-bqEs-OSQ8,7272
|
|
51
|
+
trilogy/core/processing/nodes/merge_node.py,sha256=lHRL1CpiFUocqyMVzGt97VAH_aUeHAEjebGFL1YwHys,14754
|
|
52
|
+
trilogy/core/processing/nodes/select_node_v2.py,sha256=cwbVuiu81EsX86cHCmiZSydTNJ1V3ODuXQaWO1HU4xQ,8207
|
|
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=TtQ3ATAaXKVsrhMA9EGkSS-vIC_jX0nHCzK5GBrgvrg,38511
|
|
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.58.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
83
|
+
pytrilogy-0.0.2.58.dist-info/METADATA,sha256=PYvDIfNL0E2TRIBxawpJ_SyhNDfc2TWlFnL5uOeGO0E,8823
|
|
84
|
+
pytrilogy-0.0.2.58.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
85
|
+
pytrilogy-0.0.2.58.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
|
|
86
|
+
pytrilogy-0.0.2.58.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
87
|
+
pytrilogy-0.0.2.58.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
|
)
|
|
@@ -1863,11 +1870,11 @@ class SelectStatement(HasUUID, Mergeable, Namespaced, SelectTypeMixin, BaseModel
|
|
|
1863
1870
|
return output
|
|
1864
1871
|
|
|
1865
1872
|
@property
|
|
1866
|
-
def hidden_components(self) ->
|
|
1867
|
-
output =
|
|
1873
|
+
def hidden_components(self) -> set[str]:
|
|
1874
|
+
output = set()
|
|
1868
1875
|
for item in self.selection:
|
|
1869
1876
|
if isinstance(item, SelectItem) and Modifier.HIDDEN in item.modifiers:
|
|
1870
|
-
output.
|
|
1877
|
+
output.add(item.output.address)
|
|
1871
1878
|
return output
|
|
1872
1879
|
|
|
1873
1880
|
@property
|
|
@@ -2097,10 +2104,10 @@ class MultiSelectStatement(HasUUID, SelectTypeMixin, Mergeable, Namespaced, Base
|
|
|
2097
2104
|
|
|
2098
2105
|
@computed_field # type: ignore
|
|
2099
2106
|
@cached_property
|
|
2100
|
-
def hidden_components(self) ->
|
|
2101
|
-
output =
|
|
2107
|
+
def hidden_components(self) -> set[str]:
|
|
2108
|
+
output: set[str] = set()
|
|
2102
2109
|
for select in self.selects:
|
|
2103
|
-
output
|
|
2110
|
+
output = output.union(select.hidden_components)
|
|
2104
2111
|
return output
|
|
2105
2112
|
|
|
2106
2113
|
|
|
@@ -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(
|
|
@@ -2507,7 +2508,7 @@ class QueryDatasource(BaseModel):
|
|
|
2507
2508
|
filter_concepts: List[Concept] = Field(default_factory=list)
|
|
2508
2509
|
source_type: SourceType = SourceType.SELECT
|
|
2509
2510
|
partial_concepts: List[Concept] = Field(default_factory=list)
|
|
2510
|
-
hidden_concepts:
|
|
2511
|
+
hidden_concepts: set[str] = Field(default_factory=set)
|
|
2511
2512
|
nullable_concepts: List[Concept] = Field(default_factory=list)
|
|
2512
2513
|
join_derived_concepts: List[Concept] = Field(default_factory=list)
|
|
2513
2514
|
force_group: bool | None = None
|
|
@@ -2659,10 +2660,10 @@ class QueryDatasource(BaseModel):
|
|
|
2659
2660
|
final_source_map[k] = set(
|
|
2660
2661
|
merged_datasources.get(x.safe_identifier, x) for x in list(v)
|
|
2661
2662
|
)
|
|
2662
|
-
self_hidden = self.hidden_concepts or
|
|
2663
|
-
other_hidden = other.hidden_concepts or
|
|
2663
|
+
self_hidden: set[str] = self.hidden_concepts or set()
|
|
2664
|
+
other_hidden: set[str] = other.hidden_concepts or set()
|
|
2664
2665
|
# hidden is the minimum overlapping set
|
|
2665
|
-
hidden =
|
|
2666
|
+
hidden = self_hidden.intersection(other_hidden)
|
|
2666
2667
|
qds = QueryDatasource(
|
|
2667
2668
|
input_concepts=unique(
|
|
2668
2669
|
self.input_concepts + other.input_concepts, "address"
|
|
@@ -2760,7 +2761,7 @@ class CTE(BaseModel):
|
|
|
2760
2761
|
partial_concepts: List[Concept] = Field(default_factory=list)
|
|
2761
2762
|
nullable_concepts: List[Concept] = Field(default_factory=list)
|
|
2762
2763
|
join_derived_concepts: List[Concept] = Field(default_factory=list)
|
|
2763
|
-
hidden_concepts:
|
|
2764
|
+
hidden_concepts: set[str] = Field(default_factory=set)
|
|
2764
2765
|
order_by: Optional[OrderBy] = None
|
|
2765
2766
|
limit: Optional[int] = None
|
|
2766
2767
|
base_name_override: Optional[str] = None
|
|
@@ -2946,10 +2947,10 @@ class CTE(BaseModel):
|
|
|
2946
2947
|
f" {self.name} {other.name} conditions {self.condition} {other.condition}"
|
|
2947
2948
|
)
|
|
2948
2949
|
raise ValueError(error)
|
|
2949
|
-
mutually_hidden =
|
|
2950
|
+
mutually_hidden = set()
|
|
2950
2951
|
for concept in self.hidden_concepts:
|
|
2951
|
-
if concept
|
|
2952
|
-
mutually_hidden.
|
|
2952
|
+
if concept in other.hidden_concepts:
|
|
2953
|
+
mutually_hidden.add(concept)
|
|
2953
2954
|
self.partial_concepts = unique(
|
|
2954
2955
|
self.partial_concepts + other.partial_concepts, "address"
|
|
2955
2956
|
)
|
|
@@ -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 (
|
|
@@ -3125,7 +3132,7 @@ class UnionCTE(BaseModel):
|
|
|
3125
3132
|
operator: str = "UNION ALL"
|
|
3126
3133
|
order_by: Optional[OrderBy] = None
|
|
3127
3134
|
limit: Optional[int] = None
|
|
3128
|
-
hidden_concepts:
|
|
3135
|
+
hidden_concepts: set[str] = Field(default_factory=set)
|
|
3129
3136
|
partial_concepts: list[Concept] = Field(default_factory=list)
|
|
3130
3137
|
existence_source_map: Dict[str, list[str]] = Field(default_factory=dict)
|
|
3131
3138
|
|
|
@@ -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)
|
|
@@ -4496,7 +4504,7 @@ class ProcessedQuery(BaseModel):
|
|
|
4496
4504
|
base: CTE | UnionCTE
|
|
4497
4505
|
joins: List[Join]
|
|
4498
4506
|
grain: Grain
|
|
4499
|
-
hidden_columns:
|
|
4507
|
+
hidden_columns: set[str] = Field(default_factory=set)
|
|
4500
4508
|
limit: Optional[int] = None
|
|
4501
4509
|
where_clause: Optional[WhereClause] = None
|
|
4502
4510
|
having_clause: Optional[HavingClause] = None
|
trilogy/core/optimization.py
CHANGED
|
@@ -136,15 +136,12 @@ def is_direct_return_eligible(cte: CTE | UnionCTE) -> CTE | UnionCTE | None:
|
|
|
136
136
|
|
|
137
137
|
assert isinstance(cte, CTE)
|
|
138
138
|
derived_concepts = [
|
|
139
|
-
c
|
|
140
|
-
for c in cte.source.output_concepts + cte.source.hidden_concepts
|
|
141
|
-
if c not in cte.source.input_concepts
|
|
139
|
+
c for c in cte.source.output_concepts if c not in cte.source.input_concepts
|
|
142
140
|
]
|
|
143
141
|
|
|
144
142
|
parent_derived_concepts = [
|
|
145
143
|
c
|
|
146
144
|
for c in direct_parent.source.output_concepts
|
|
147
|
-
+ direct_parent.source.hidden_concepts
|
|
148
145
|
if c not in direct_parent.source.input_concepts
|
|
149
146
|
]
|
|
150
147
|
condition_arguments = cte.condition.row_arguments if cte.condition else []
|
|
@@ -180,8 +177,8 @@ def optimize_ctes(
|
|
|
180
177
|
):
|
|
181
178
|
direct_parent.order_by = root_cte.order_by
|
|
182
179
|
direct_parent.limit = root_cte.limit
|
|
183
|
-
direct_parent.hidden_concepts = (
|
|
184
|
-
|
|
180
|
+
direct_parent.hidden_concepts = root_cte.hidden_concepts.union(
|
|
181
|
+
direct_parent.hidden_concepts
|
|
185
182
|
)
|
|
186
183
|
if root_cte.condition:
|
|
187
184
|
if direct_parent.condition:
|
|
@@ -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,
|
|
@@ -577,7 +580,7 @@ def validate_stack(
|
|
|
577
580
|
resolved = node.resolve()
|
|
578
581
|
|
|
579
582
|
for concept in resolved.output_concepts:
|
|
580
|
-
if concept in resolved.hidden_concepts:
|
|
583
|
+
if concept.address in resolved.hidden_concepts:
|
|
581
584
|
continue
|
|
582
585
|
validate_concept(
|
|
583
586
|
concept,
|
|
@@ -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
|
)
|
|
@@ -208,7 +208,7 @@ def gen_enrichment_node(
|
|
|
208
208
|
non_hidden = [
|
|
209
209
|
x
|
|
210
210
|
for x in base_node.output_concepts
|
|
211
|
-
if x.address not in
|
|
211
|
+
if x.address not in base_node.hidden_concepts
|
|
212
212
|
]
|
|
213
213
|
return MergeNode(
|
|
214
214
|
input_concepts=unique(join_keys + extra_required + non_hidden, "address"),
|
|
@@ -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=set(
|
|
53
|
+
[group_arg.address]
|
|
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=set([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,
|
|
@@ -47,7 +47,7 @@ def gen_rowset_node(
|
|
|
47
47
|
return None
|
|
48
48
|
enrichment = set([x.address for x in local_optional])
|
|
49
49
|
rowset_relevant = [x for x in rowset.derived_concepts]
|
|
50
|
-
select_hidden =
|
|
50
|
+
select_hidden = select.hidden_components
|
|
51
51
|
rowset_hidden = [
|
|
52
52
|
x
|
|
53
53
|
for x in rowset.derived_concepts
|
|
@@ -80,7 +80,9 @@ def gen_rowset_node(
|
|
|
80
80
|
for x in node.output_concepts
|
|
81
81
|
if x.address
|
|
82
82
|
not in [
|
|
83
|
-
y
|
|
83
|
+
y
|
|
84
|
+
for y in node.hidden_concepts
|
|
85
|
+
if environment.concepts[y].derivation != PurposeLineage.ROWSET
|
|
84
86
|
]
|
|
85
87
|
],
|
|
86
88
|
)
|
|
@@ -103,6 +105,7 @@ def gen_rowset_node(
|
|
|
103
105
|
)
|
|
104
106
|
return node
|
|
105
107
|
if any(x.derivation == PurposeLineage.ROWSET for x in possible_joins):
|
|
108
|
+
|
|
106
109
|
logger.info(
|
|
107
110
|
f"{padding(depth)}{LOGGER_PREFIX} cannot enrich rowset node with rowset concepts; exiting early"
|
|
108
111
|
)
|
|
@@ -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
|
|
|
@@ -40,9 +40,10 @@ def resolve_concept_map(
|
|
|
40
40
|
for concept in input.output_concepts:
|
|
41
41
|
if concept.address not in input.non_partial_concept_addresses:
|
|
42
42
|
continue
|
|
43
|
-
if
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
if (
|
|
44
|
+
isinstance(input, QueryDatasource)
|
|
45
|
+
and concept.address in input.hidden_concepts
|
|
46
|
+
):
|
|
46
47
|
continue
|
|
47
48
|
if concept.address in full_addresses:
|
|
48
49
|
concept_map[concept.address].add(input)
|
|
@@ -138,7 +139,7 @@ class StrategyNode:
|
|
|
138
139
|
preexisting_conditions: Conditional | Comparison | Parenthetical | None = None,
|
|
139
140
|
force_group: bool | None = None,
|
|
140
141
|
grain: Optional[Grain] = None,
|
|
141
|
-
hidden_concepts:
|
|
142
|
+
hidden_concepts: set[str] | None = None,
|
|
142
143
|
existence_concepts: List[Concept] | None = None,
|
|
143
144
|
virtual_output_concepts: List[Concept] | None = None,
|
|
144
145
|
):
|
|
@@ -165,7 +166,7 @@ class StrategyNode:
|
|
|
165
166
|
self.grain = grain
|
|
166
167
|
self.force_group = force_group
|
|
167
168
|
self.tainted = False
|
|
168
|
-
self.hidden_concepts = hidden_concepts or
|
|
169
|
+
self.hidden_concepts = hidden_concepts or set()
|
|
169
170
|
self.existence_concepts = existence_concepts or []
|
|
170
171
|
self.virtual_output_concepts = virtual_output_concepts or []
|
|
171
172
|
self.preexisting_conditions = preexisting_conditions
|
|
@@ -192,6 +193,8 @@ class StrategyNode:
|
|
|
192
193
|
for x in self.parents:
|
|
193
194
|
for z in x.usable_outputs:
|
|
194
195
|
non_hidden.add(z.address)
|
|
196
|
+
for psd in z.pseudonyms:
|
|
197
|
+
non_hidden.add(psd)
|
|
195
198
|
if not all([x.address in non_hidden for x in self.input_concepts]):
|
|
196
199
|
missing = [x for x in self.input_concepts if x.address not in non_hidden]
|
|
197
200
|
raise ValueError(
|
|
@@ -246,6 +249,15 @@ class StrategyNode:
|
|
|
246
249
|
self.rebuild_cache()
|
|
247
250
|
return self
|
|
248
251
|
|
|
252
|
+
def add_partial_concepts(self, concepts: List[Concept], rebuild: bool = True):
|
|
253
|
+
for concept in concepts:
|
|
254
|
+
if concept.address not in self.partial_lcl.addresses:
|
|
255
|
+
self.partial_concepts.append(concept)
|
|
256
|
+
self.partial_lcl = LooseConceptList(concepts=self.partial_concepts)
|
|
257
|
+
if rebuild:
|
|
258
|
+
self.rebuild_cache()
|
|
259
|
+
return self
|
|
260
|
+
|
|
249
261
|
def add_existence_concepts(self, concepts: List[Concept], rebuild: bool = True):
|
|
250
262
|
for concept in concepts:
|
|
251
263
|
if concept.address not in self.output_concepts:
|
|
@@ -270,22 +282,20 @@ class StrategyNode:
|
|
|
270
282
|
|
|
271
283
|
def hide_output_concepts(self, concepts: List[Concept], rebuild: bool = True):
|
|
272
284
|
for x in concepts:
|
|
273
|
-
self.hidden_concepts.
|
|
285
|
+
self.hidden_concepts.add(x.address)
|
|
274
286
|
if rebuild:
|
|
275
287
|
self.rebuild_cache()
|
|
276
288
|
return self
|
|
277
289
|
|
|
278
290
|
def unhide_output_concepts(self, concepts: List[Concept], rebuild: bool = True):
|
|
279
|
-
self.hidden_concepts =
|
|
280
|
-
x for x in self.hidden_concepts if x.address not in concepts
|
|
281
|
-
]
|
|
291
|
+
self.hidden_concepts = set(x for x in self.hidden_concepts if x not in concepts)
|
|
282
292
|
if rebuild:
|
|
283
293
|
self.rebuild_cache()
|
|
284
294
|
return self
|
|
285
295
|
|
|
286
296
|
def remove_output_concepts(self, concepts: List[Concept], rebuild: bool = True):
|
|
287
297
|
for x in concepts:
|
|
288
|
-
self.hidden_concepts.
|
|
298
|
+
self.hidden_concepts.add(x.address)
|
|
289
299
|
addresses = [x.address for x in concepts]
|
|
290
300
|
self.output_concepts = [
|
|
291
301
|
x for x in self.output_concepts if x.address not in addresses
|
|
@@ -377,7 +387,7 @@ class StrategyNode:
|
|
|
377
387
|
preexisting_conditions=self.preexisting_conditions,
|
|
378
388
|
force_group=self.force_group,
|
|
379
389
|
grain=self.grain,
|
|
380
|
-
hidden_concepts=
|
|
390
|
+
hidden_concepts=set(self.hidden_concepts),
|
|
381
391
|
existence_concepts=list(self.existence_concepts),
|
|
382
392
|
virtual_output_concepts=list(self.virtual_output_concepts),
|
|
383
393
|
)
|
|
@@ -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
|
|
|
@@ -41,7 +48,7 @@ class GroupNode(StrategyNode):
|
|
|
41
48
|
conditions: Conditional | Comparison | Parenthetical | None = None,
|
|
42
49
|
preexisting_conditions: Conditional | Comparison | Parenthetical | None = None,
|
|
43
50
|
existence_concepts: List[Concept] | None = None,
|
|
44
|
-
hidden_concepts:
|
|
51
|
+
hidden_concepts: set[str] | None = None,
|
|
45
52
|
):
|
|
46
53
|
super().__init__(
|
|
47
54
|
input_concepts=input_concepts,
|
|
@@ -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,
|
|
@@ -157,9 +154,9 @@ class GroupNode(StrategyNode):
|
|
|
157
154
|
base.output_concepts + self.conditions.row_arguments, "address"
|
|
158
155
|
)
|
|
159
156
|
# re-visible any hidden concepts
|
|
160
|
-
base.hidden_concepts =
|
|
161
|
-
x for x in base.hidden_concepts if x not in base.output_concepts
|
|
162
|
-
|
|
157
|
+
base.hidden_concepts = set(
|
|
158
|
+
[x for x in base.hidden_concepts if x not in base.output_concepts]
|
|
159
|
+
)
|
|
163
160
|
source_map = resolve_concept_map(
|
|
164
161
|
[base],
|
|
165
162
|
targets=self.output_concepts,
|
|
@@ -194,5 +191,5 @@ class GroupNode(StrategyNode):
|
|
|
194
191
|
conditions=self.conditions,
|
|
195
192
|
preexisting_conditions=self.preexisting_conditions,
|
|
196
193
|
existence_concepts=list(self.existence_concepts),
|
|
197
|
-
hidden_concepts=
|
|
194
|
+
hidden_concepts=set(self.hidden_concepts),
|
|
198
195
|
)
|
|
@@ -115,7 +115,7 @@ class MergeNode(StrategyNode):
|
|
|
115
115
|
grain: Grain | None = None,
|
|
116
116
|
conditions: Conditional | Comparison | Parenthetical | None = None,
|
|
117
117
|
preexisting_conditions: Conditional | Comparison | Parenthetical | None = None,
|
|
118
|
-
hidden_concepts:
|
|
118
|
+
hidden_concepts: set[str] | None = None,
|
|
119
119
|
virtual_output_concepts: List[Concept] | None = None,
|
|
120
120
|
existence_concepts: List[Concept] | None = None,
|
|
121
121
|
):
|
|
@@ -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:
|
|
@@ -368,7 +369,7 @@ class MergeNode(StrategyNode):
|
|
|
368
369
|
conditions=self.conditions,
|
|
369
370
|
preexisting_conditions=self.preexisting_conditions,
|
|
370
371
|
nullable_concepts=list(self.nullable_concepts),
|
|
371
|
-
hidden_concepts=
|
|
372
|
+
hidden_concepts=set(self.hidden_concepts),
|
|
372
373
|
virtual_output_concepts=list(self.virtual_output_concepts),
|
|
373
374
|
node_joins=list(self.node_joins) if self.node_joins else None,
|
|
374
375
|
join_concepts=list(self.join_concepts) if self.join_concepts else None,
|
|
@@ -45,7 +45,7 @@ class SelectNode(StrategyNode):
|
|
|
45
45
|
force_group: bool | None = False,
|
|
46
46
|
conditions: Conditional | Comparison | Parenthetical | None = None,
|
|
47
47
|
preexisting_conditions: Conditional | Comparison | Parenthetical | None = None,
|
|
48
|
-
hidden_concepts:
|
|
48
|
+
hidden_concepts: set[str] | None = None,
|
|
49
49
|
):
|
|
50
50
|
super().__init__(
|
|
51
51
|
input_concepts=input_concepts,
|
|
@@ -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.address in datasource.hidden_concepts:
|
|
351
|
+
continue
|
|
353
352
|
add_node_join_concept(
|
|
354
353
|
graph=graph,
|
|
355
354
|
concept=concept,
|
|
@@ -568,9 +567,8 @@ def find_nullable_concepts(
|
|
|
568
567
|
def sort_select_output_processed(
|
|
569
568
|
cte: CTE | UnionCTE, query: ProcessedQuery
|
|
570
569
|
) -> CTE | UnionCTE:
|
|
571
|
-
hidden_addresses = [c.address for c in query.hidden_columns]
|
|
572
570
|
output_addresses = [
|
|
573
|
-
c.address for c in query.output_columns if c.address not in
|
|
571
|
+
c.address for c in query.output_columns if c.address not in query.hidden_columns
|
|
574
572
|
]
|
|
575
573
|
|
|
576
574
|
mapping = {x.address: x for x in cte.output_columns}
|
|
@@ -587,9 +585,10 @@ def sort_select_output(
|
|
|
587
585
|
) -> CTE | UnionCTE:
|
|
588
586
|
if isinstance(query, ProcessedQuery):
|
|
589
587
|
return sort_select_output_processed(cte, query)
|
|
590
|
-
hidden_addresses = [c.address for c in query.hidden_components]
|
|
591
588
|
output_addresses = [
|
|
592
|
-
c.address
|
|
589
|
+
c.address
|
|
590
|
+
for c in query.output_components
|
|
591
|
+
if c.address not in query.hidden_components
|
|
593
592
|
]
|
|
594
593
|
|
|
595
594
|
mapping = {x.address: x for x in cte.output_columns}
|
trilogy/core/query_processor.py
CHANGED
|
@@ -500,7 +500,7 @@ def process_query(
|
|
|
500
500
|
deduped_ctes: List[CTE | UnionCTE] = list(seen.values())
|
|
501
501
|
root_cte.order_by = statement.order_by
|
|
502
502
|
root_cte.limit = statement.limit
|
|
503
|
-
root_cte.hidden_concepts =
|
|
503
|
+
root_cte.hidden_concepts = statement.hidden_components
|
|
504
504
|
|
|
505
505
|
final_ctes = optimize_ctes(deduped_ctes, root_cte, statement)
|
|
506
506
|
return ProcessedQuery(
|
|
@@ -514,6 +514,6 @@ def process_query(
|
|
|
514
514
|
base=root_cte,
|
|
515
515
|
# we no longer do any joins at final level, this should always happen in parent CTEs
|
|
516
516
|
joins=[],
|
|
517
|
-
hidden_columns=[x for x in statement.hidden_components],
|
|
517
|
+
hidden_columns=set([x for x in statement.hidden_components]),
|
|
518
518
|
local_concepts=statement.local_concepts,
|
|
519
519
|
)
|
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:
|
|
@@ -658,7 +660,7 @@ class BaseDialect:
|
|
|
658
660
|
self.render_concept_sql(c, cte)
|
|
659
661
|
for c in cte.output_columns
|
|
660
662
|
if c.address not in [y.address for y in cte.join_derived_concepts]
|
|
661
|
-
and c.address not in
|
|
663
|
+
and c.address not in cte.hidden_concepts
|
|
662
664
|
] + [
|
|
663
665
|
f"{self.QUOTE_CHARACTER}{c.safe_address}{self.QUOTE_CHARACTER}"
|
|
664
666
|
for c in cte.join_derived_concepts
|
|
@@ -668,7 +670,7 @@ class BaseDialect:
|
|
|
668
670
|
select_columns = [
|
|
669
671
|
self.render_concept_sql(c, cte)
|
|
670
672
|
for c in cte.output_columns
|
|
671
|
-
if c.address not in
|
|
673
|
+
if c.address not in cte.hidden_concepts
|
|
672
674
|
]
|
|
673
675
|
if auto_sort:
|
|
674
676
|
select_columns = sorted(select_columns, key=lambda x: x)
|
|
@@ -884,9 +886,10 @@ class BaseDialect:
|
|
|
884
886
|
select_columns: Dict[str, str] = {}
|
|
885
887
|
cte_output_map = {}
|
|
886
888
|
selected = set()
|
|
887
|
-
hidden_addresses = [c.address for c in query.hidden_columns]
|
|
888
889
|
output_addresses = [
|
|
889
|
-
c.address
|
|
890
|
+
c.address
|
|
891
|
+
for c in query.output_columns
|
|
892
|
+
if c.address not in query.hidden_columns
|
|
890
893
|
]
|
|
891
894
|
|
|
892
895
|
for c in query.base.output_columns:
|
|
@@ -895,7 +898,7 @@ class BaseDialect:
|
|
|
895
898
|
f"{query.base.name}.{safe_quote(c.safe_address, self.QUOTE_CHARACTER)}"
|
|
896
899
|
)
|
|
897
900
|
cte_output_map[c.address] = query.base
|
|
898
|
-
if c.address not in
|
|
901
|
+
if c.address not in query.hidden_columns:
|
|
899
902
|
selected.add(c.address)
|
|
900
903
|
if not all([x in selected for x in output_addresses]):
|
|
901
904
|
missing = [x for x in output_addresses if x not in selected]
|
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
|