pytrilogy 0.0.2.47__py3-none-any.whl → 0.0.2.49__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.47.dist-info → pytrilogy-0.0.2.49.dist-info}/METADATA +1 -1
- pytrilogy-0.0.2.49.dist-info/RECORD +85 -0
- trilogy/__init__.py +2 -2
- trilogy/constants.py +4 -2
- trilogy/core/enums.py +7 -1
- trilogy/core/env_processor.py +1 -2
- trilogy/core/environment_helpers.py +5 -5
- trilogy/core/functions.py +11 -10
- trilogy/core/internal.py +2 -3
- trilogy/core/models.py +449 -393
- trilogy/core/optimization.py +37 -21
- trilogy/core/optimizations/__init__.py +1 -1
- trilogy/core/optimizations/base_optimization.py +6 -6
- trilogy/core/optimizations/inline_constant.py +7 -4
- trilogy/core/optimizations/inline_datasource.py +14 -5
- trilogy/core/optimizations/predicate_pushdown.py +20 -10
- trilogy/core/processing/concept_strategies_v3.py +43 -24
- trilogy/core/processing/graph_utils.py +2 -3
- trilogy/core/processing/node_generators/__init__.py +7 -5
- trilogy/core/processing/node_generators/basic_node.py +4 -4
- trilogy/core/processing/node_generators/common.py +10 -11
- trilogy/core/processing/node_generators/filter_node.py +7 -9
- trilogy/core/processing/node_generators/group_node.py +10 -11
- trilogy/core/processing/node_generators/group_to_node.py +5 -5
- trilogy/core/processing/node_generators/multiselect_node.py +10 -12
- trilogy/core/processing/node_generators/node_merge_node.py +7 -9
- trilogy/core/processing/node_generators/rowset_node.py +36 -15
- trilogy/core/processing/node_generators/select_merge_node.py +11 -10
- trilogy/core/processing/node_generators/select_node.py +5 -5
- trilogy/core/processing/node_generators/union_node.py +75 -0
- trilogy/core/processing/node_generators/unnest_node.py +2 -3
- trilogy/core/processing/node_generators/window_node.py +3 -4
- trilogy/core/processing/nodes/__init__.py +9 -5
- trilogy/core/processing/nodes/base_node.py +45 -13
- trilogy/core/processing/nodes/filter_node.py +3 -4
- trilogy/core/processing/nodes/group_node.py +17 -13
- trilogy/core/processing/nodes/merge_node.py +14 -12
- trilogy/core/processing/nodes/select_node_v2.py +13 -9
- trilogy/core/processing/nodes/union_node.py +50 -0
- trilogy/core/processing/nodes/unnest_node.py +2 -3
- trilogy/core/processing/nodes/window_node.py +2 -3
- trilogy/core/processing/utility.py +38 -41
- trilogy/core/query_processor.py +71 -51
- trilogy/dialect/base.py +95 -53
- trilogy/dialect/bigquery.py +2 -3
- trilogy/dialect/common.py +5 -4
- trilogy/dialect/config.py +0 -2
- trilogy/dialect/duckdb.py +2 -2
- trilogy/dialect/enums.py +5 -5
- trilogy/dialect/postgres.py +2 -2
- trilogy/dialect/presto.py +3 -4
- trilogy/dialect/snowflake.py +2 -2
- trilogy/dialect/sql_server.py +3 -4
- trilogy/engine.py +2 -1
- trilogy/executor.py +43 -30
- trilogy/hooks/base_hook.py +5 -4
- trilogy/hooks/graph_hook.py +2 -1
- trilogy/hooks/query_debugger.py +18 -8
- trilogy/parsing/common.py +15 -20
- trilogy/parsing/parse_engine.py +125 -88
- trilogy/parsing/render.py +32 -35
- trilogy/parsing/trilogy.lark +8 -1
- trilogy/scripts/trilogy.py +6 -4
- trilogy/utility.py +1 -1
- pytrilogy-0.0.2.47.dist-info/RECORD +0 -83
- {pytrilogy-0.0.2.47.dist-info → pytrilogy-0.0.2.49.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.47.dist-info → pytrilogy-0.0.2.49.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.2.47.dist-info → pytrilogy-0.0.2.49.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.2.47.dist-info → pytrilogy-0.0.2.49.dist-info}/top_level.txt +0 -0
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
from typing import List, Optional
|
|
2
2
|
|
|
3
|
-
from trilogy.core.models import Concept, Environment, Conditional, WhereClause
|
|
4
|
-
from trilogy.core.processing.nodes import MergeNode, History, StrategyNode
|
|
5
3
|
import networkx as nx
|
|
6
|
-
from
|
|
4
|
+
from networkx.algorithms import approximation as ax
|
|
5
|
+
|
|
7
6
|
from trilogy.constants import logger
|
|
8
|
-
from trilogy.
|
|
7
|
+
from trilogy.core.enums import PurposeLineage
|
|
9
8
|
from trilogy.core.exceptions import AmbiguousRelationshipResolutionException
|
|
9
|
+
from trilogy.core.graph_models import concept_to_node
|
|
10
|
+
from trilogy.core.models import Concept, Conditional, Environment, WhereClause
|
|
11
|
+
from trilogy.core.processing.nodes import History, MergeNode, StrategyNode
|
|
10
12
|
from trilogy.core.processing.utility import padding
|
|
11
|
-
from
|
|
12
|
-
from trilogy.core.enums import PurposeLineage
|
|
13
|
-
|
|
13
|
+
from trilogy.utility import unique
|
|
14
14
|
|
|
15
15
|
LOGGER_PREFIX = "[GEN_MERGE_NODE]"
|
|
16
16
|
AMBIGUITY_CHECK_LIMIT = 20
|
|
@@ -194,7 +194,6 @@ def resolve_weak_components(
|
|
|
194
194
|
filter_downstream: bool = True,
|
|
195
195
|
accept_partial: bool = False,
|
|
196
196
|
) -> list[list[Concept]] | None:
|
|
197
|
-
|
|
198
197
|
break_flag = False
|
|
199
198
|
found = []
|
|
200
199
|
search_graph = environment_graph.copy()
|
|
@@ -355,7 +354,6 @@ def gen_merge_node(
|
|
|
355
354
|
conditions: Conditional | None = None,
|
|
356
355
|
search_conditions: WhereClause | None = None,
|
|
357
356
|
) -> Optional[MergeNode]:
|
|
358
|
-
|
|
359
357
|
if search_conditions:
|
|
360
358
|
all_concepts = unique(all_concepts + search_conditions.row_arguments, "address")
|
|
361
359
|
for filter_downstream in [True, False]:
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from trilogy.constants import logger
|
|
4
|
+
from trilogy.core.enums import PurposeLineage
|
|
1
5
|
from trilogy.core.models import (
|
|
2
6
|
Concept,
|
|
3
7
|
Environment,
|
|
4
|
-
|
|
8
|
+
MultiSelectStatement,
|
|
5
9
|
RowsetDerivationStatement,
|
|
6
10
|
RowsetItem,
|
|
7
|
-
|
|
11
|
+
SelectStatement,
|
|
8
12
|
WhereClause,
|
|
9
13
|
)
|
|
10
|
-
from trilogy.core.processing.nodes import
|
|
11
|
-
from typing import List
|
|
12
|
-
|
|
13
|
-
from trilogy.core.enums import PurposeLineage
|
|
14
|
-
from trilogy.constants import logger
|
|
15
|
-
from trilogy.core.processing.utility import padding, concept_to_relevant_joins
|
|
14
|
+
from trilogy.core.processing.nodes import History, MergeNode, StrategyNode
|
|
16
15
|
from trilogy.core.processing.nodes.base_node import concept_list_to_grain
|
|
16
|
+
from trilogy.core.processing.utility import concept_to_relevant_joins, padding
|
|
17
17
|
|
|
18
18
|
LOGGER_PREFIX = "[GEN_ROWSET_NODE]"
|
|
19
19
|
|
|
@@ -37,7 +37,8 @@ def gen_rowset_node(
|
|
|
37
37
|
lineage: RowsetItem = concept.lineage
|
|
38
38
|
rowset: RowsetDerivationStatement = lineage.rowset
|
|
39
39
|
select: SelectStatement | MultiSelectStatement = lineage.rowset.select
|
|
40
|
-
|
|
40
|
+
|
|
41
|
+
node = get_query_node(environment, select)
|
|
41
42
|
|
|
42
43
|
if not node:
|
|
43
44
|
logger.info(
|
|
@@ -93,15 +94,22 @@ def gen_rowset_node(
|
|
|
93
94
|
logger.info(
|
|
94
95
|
f"{padding(depth)}{LOGGER_PREFIX} no enrichment required for rowset node as all optional found or no optional; exiting early."
|
|
95
96
|
)
|
|
96
|
-
# node.set_preexisting_conditions(conditions.conditional if conditions else None)
|
|
97
97
|
return node
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
possible_joins = concept_to_relevant_joins(
|
|
99
|
+
[x for x in node.output_concepts if x.derivation != PurposeLineage.ROWSET]
|
|
100
|
+
)
|
|
101
|
+
logger.info({x.address: x.keys for x in possible_joins})
|
|
100
102
|
if not possible_joins:
|
|
101
103
|
logger.info(
|
|
102
104
|
f"{padding(depth)}{LOGGER_PREFIX} no possible joins for rowset node to get {[x.address for x in local_optional]}; have {[x.address for x in node.output_concepts]}"
|
|
103
105
|
)
|
|
104
106
|
return node
|
|
107
|
+
if any(x.derivation == PurposeLineage.ROWSET for x in possible_joins):
|
|
108
|
+
logger.info(
|
|
109
|
+
f"{padding(depth)}{LOGGER_PREFIX} cannot enrich rowset node with rowset concepts; exiting early"
|
|
110
|
+
)
|
|
111
|
+
return node
|
|
112
|
+
logger.info([x.address for x in possible_joins + local_optional])
|
|
105
113
|
enrich_node: MergeNode = source_concepts( # this fetches the parent + join keys
|
|
106
114
|
# to then connect to the rest of the query
|
|
107
115
|
mandatory_list=possible_joins + local_optional,
|
|
@@ -109,15 +117,28 @@ def gen_rowset_node(
|
|
|
109
117
|
g=g,
|
|
110
118
|
depth=depth + 1,
|
|
111
119
|
conditions=conditions,
|
|
120
|
+
history=history,
|
|
112
121
|
)
|
|
113
122
|
if not enrich_node:
|
|
114
123
|
logger.info(
|
|
115
124
|
f"{padding(depth)}{LOGGER_PREFIX} Cannot generate rowset enrichment node for {concept} with optional {local_optional}, returning just rowset node"
|
|
116
125
|
)
|
|
117
126
|
return node
|
|
127
|
+
|
|
128
|
+
non_hidden = [
|
|
129
|
+
x for x in node.output_concepts if x.address not in node.hidden_concepts
|
|
130
|
+
]
|
|
131
|
+
for x in possible_joins:
|
|
132
|
+
if x.address in node.hidden_concepts:
|
|
133
|
+
node.unhide_output_concepts([x])
|
|
134
|
+
non_hidden_enrich = [
|
|
135
|
+
x
|
|
136
|
+
for x in enrich_node.output_concepts
|
|
137
|
+
if x.address not in enrich_node.hidden_concepts
|
|
138
|
+
]
|
|
118
139
|
return MergeNode(
|
|
119
|
-
input_concepts=
|
|
120
|
-
output_concepts=
|
|
140
|
+
input_concepts=non_hidden + non_hidden_enrich,
|
|
141
|
+
output_concepts=non_hidden + local_optional,
|
|
121
142
|
environment=environment,
|
|
122
143
|
g=g,
|
|
123
144
|
depth=depth,
|
|
@@ -125,6 +146,6 @@ def gen_rowset_node(
|
|
|
125
146
|
node,
|
|
126
147
|
enrich_node,
|
|
127
148
|
],
|
|
128
|
-
partial_concepts=node.partial_concepts,
|
|
149
|
+
partial_concepts=node.partial_concepts + enrich_node.partial_concepts,
|
|
129
150
|
preexisting_conditions=conditions.conditional if conditions else None,
|
|
130
151
|
)
|
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
from typing import List, Optional
|
|
2
2
|
|
|
3
|
+
import networkx as nx
|
|
4
|
+
|
|
5
|
+
from trilogy.constants import logger
|
|
6
|
+
from trilogy.core.enums import PurposeLineage
|
|
7
|
+
from trilogy.core.graph_models import concept_to_node
|
|
3
8
|
from trilogy.core.models import (
|
|
4
9
|
Concept,
|
|
10
|
+
Datasource,
|
|
5
11
|
Environment,
|
|
6
12
|
Grain,
|
|
7
|
-
Datasource,
|
|
8
|
-
WhereClause,
|
|
9
13
|
LooseConceptList,
|
|
14
|
+
WhereClause,
|
|
10
15
|
)
|
|
11
16
|
from trilogy.core.processing.nodes import (
|
|
12
|
-
MergeNode,
|
|
13
|
-
StrategyNode,
|
|
14
|
-
GroupNode,
|
|
15
17
|
ConstantNode,
|
|
18
|
+
GroupNode,
|
|
19
|
+
MergeNode,
|
|
16
20
|
SelectNode,
|
|
21
|
+
StrategyNode,
|
|
17
22
|
)
|
|
18
|
-
import networkx as nx
|
|
19
|
-
from trilogy.core.graph_models import concept_to_node
|
|
20
|
-
from trilogy.constants import logger
|
|
21
|
-
from trilogy.core.processing.utility import padding
|
|
22
|
-
from trilogy.core.enums import PurposeLineage
|
|
23
23
|
from trilogy.core.processing.nodes.base_node import (
|
|
24
24
|
concept_list_to_grain,
|
|
25
25
|
)
|
|
26
|
+
from trilogy.core.processing.utility import padding
|
|
26
27
|
|
|
27
28
|
LOGGER_PREFIX = "[GEN_ROOT_MERGE_NODE]"
|
|
28
29
|
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
+
from trilogy.constants import logger
|
|
1
2
|
from trilogy.core.enums import PurposeLineage
|
|
3
|
+
from trilogy.core.exceptions import NoDatasourceException
|
|
2
4
|
from trilogy.core.models import (
|
|
3
5
|
Concept,
|
|
4
6
|
Environment,
|
|
5
7
|
LooseConceptList,
|
|
6
8
|
WhereClause,
|
|
7
9
|
)
|
|
10
|
+
from trilogy.core.processing.node_generators.select_merge_node import (
|
|
11
|
+
gen_select_merge_node,
|
|
12
|
+
)
|
|
8
13
|
from trilogy.core.processing.nodes import (
|
|
9
14
|
StrategyNode,
|
|
10
15
|
)
|
|
11
|
-
from trilogy.core.exceptions import NoDatasourceException
|
|
12
|
-
from trilogy.constants import logger
|
|
13
16
|
from trilogy.core.processing.utility import padding
|
|
14
|
-
from trilogy.core.processing.node_generators.select_merge_node import (
|
|
15
|
-
gen_select_merge_node,
|
|
16
|
-
)
|
|
17
17
|
|
|
18
18
|
LOGGER_PREFIX = "[GEN_SELECT_NODE]"
|
|
19
19
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from trilogy.constants import logger
|
|
4
|
+
from trilogy.core.enums import FunctionType, Purpose
|
|
5
|
+
from trilogy.core.models import Concept, Function, WhereClause
|
|
6
|
+
from trilogy.core.processing.nodes import History, StrategyNode, UnionNode
|
|
7
|
+
from trilogy.core.processing.utility import padding
|
|
8
|
+
|
|
9
|
+
LOGGER_PREFIX = "[GEN_UNION_NODE]"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def is_union(c: Concept):
|
|
13
|
+
return isinstance(c.lineage, Function) and c.lineage.operator == FunctionType.UNION
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def gen_union_node(
|
|
17
|
+
concept: Concept,
|
|
18
|
+
local_optional: List[Concept],
|
|
19
|
+
environment,
|
|
20
|
+
g,
|
|
21
|
+
depth: int,
|
|
22
|
+
source_concepts,
|
|
23
|
+
history: History | None = None,
|
|
24
|
+
conditions: WhereClause | None = None,
|
|
25
|
+
) -> StrategyNode | None:
|
|
26
|
+
all_unions = [x for x in local_optional if is_union(x)] + [concept]
|
|
27
|
+
|
|
28
|
+
parents = []
|
|
29
|
+
keys = [x for x in all_unions if x.purpose == Purpose.KEY]
|
|
30
|
+
base = keys.pop()
|
|
31
|
+
remaining = [x for x in all_unions if x.address != base.address]
|
|
32
|
+
arguments = []
|
|
33
|
+
if isinstance(base.lineage, Function):
|
|
34
|
+
arguments = base.lineage.concept_arguments
|
|
35
|
+
for arg in arguments:
|
|
36
|
+
relevant_parents: list[Concept] = []
|
|
37
|
+
for other_union in remaining:
|
|
38
|
+
assert other_union.lineage
|
|
39
|
+
potential_parents = [
|
|
40
|
+
z for z in other_union.lineage.arguments if isinstance(z, Concept)
|
|
41
|
+
]
|
|
42
|
+
relevant_parents += [
|
|
43
|
+
x for x in potential_parents if x.keys and arg.address in x.keys
|
|
44
|
+
]
|
|
45
|
+
logger.info(
|
|
46
|
+
f"For parent arg {arg.address}, including additional union inputs {[c.address for c in relevant_parents]}"
|
|
47
|
+
)
|
|
48
|
+
parent: StrategyNode = source_concepts(
|
|
49
|
+
mandatory_list=[arg] + relevant_parents,
|
|
50
|
+
environment=environment,
|
|
51
|
+
g=g,
|
|
52
|
+
depth=depth + 1,
|
|
53
|
+
history=history,
|
|
54
|
+
conditions=conditions,
|
|
55
|
+
)
|
|
56
|
+
parent.hide_output_concepts(parent.output_concepts)
|
|
57
|
+
# parent.remove_output_concepts(parent.output_concepts)
|
|
58
|
+
parent.add_output_concept(concept)
|
|
59
|
+
for x in remaining:
|
|
60
|
+
parent.add_output_concept(x)
|
|
61
|
+
|
|
62
|
+
parents.append(parent)
|
|
63
|
+
if not parent:
|
|
64
|
+
logger.info(
|
|
65
|
+
f"{padding(depth)}{LOGGER_PREFIX} could not find union node parents"
|
|
66
|
+
)
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
return UnionNode(
|
|
70
|
+
input_concepts=[concept] + local_optional,
|
|
71
|
+
output_concepts=[concept] + local_optional,
|
|
72
|
+
environment=environment,
|
|
73
|
+
g=g,
|
|
74
|
+
parents=parents,
|
|
75
|
+
)
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
from trilogy.constants import logger
|
|
4
4
|
from trilogy.core.models import Concept, Function, WhereClause
|
|
5
|
-
from trilogy.core.processing.nodes import
|
|
5
|
+
from trilogy.core.processing.nodes import History, StrategyNode, UnnestNode
|
|
6
6
|
from trilogy.core.processing.utility import padding
|
|
7
|
-
from trilogy.constants import logger
|
|
8
7
|
|
|
9
8
|
LOGGER_PREFIX = "[GEN_UNNEST_NODE]"
|
|
10
9
|
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
from trilogy.core.models import Concept, WindowItem, Environment, WhereClause
|
|
5
|
-
from trilogy.utility import unique
|
|
6
|
-
from trilogy.core.processing.nodes import WindowNode, StrategyNode, History
|
|
7
3
|
from trilogy.constants import logger
|
|
4
|
+
from trilogy.core.models import Concept, Environment, WhereClause, WindowItem
|
|
5
|
+
from trilogy.core.processing.nodes import History, StrategyNode, WindowNode
|
|
8
6
|
from trilogy.core.processing.utility import padding
|
|
7
|
+
from trilogy.utility import unique
|
|
9
8
|
|
|
10
9
|
LOGGER_PREFIX = "[GEN_WINDOW_NODE]"
|
|
11
10
|
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
2
|
+
|
|
3
|
+
from trilogy.core.models import Concept, Environment, WhereClause
|
|
4
|
+
|
|
5
|
+
from .base_node import NodeJoin, StrategyNode
|
|
1
6
|
from .filter_node import FilterNode
|
|
2
7
|
from .group_node import GroupNode
|
|
3
8
|
from .merge_node import MergeNode
|
|
4
|
-
from .select_node_v2 import
|
|
5
|
-
from .
|
|
6
|
-
from .base_node import StrategyNode, NodeJoin
|
|
9
|
+
from .select_node_v2 import ConstantNode, SelectNode
|
|
10
|
+
from .union_node import UnionNode
|
|
7
11
|
from .unnest_node import UnnestNode
|
|
8
|
-
from
|
|
9
|
-
from trilogy.core.models import Concept, Environment, WhereClause
|
|
12
|
+
from .window_node import WindowNode
|
|
10
13
|
|
|
11
14
|
|
|
12
15
|
class History(BaseModel):
|
|
@@ -160,5 +163,6 @@ __all__ = [
|
|
|
160
163
|
"NodeJoin",
|
|
161
164
|
"ConstantNode",
|
|
162
165
|
"UnnestNode",
|
|
166
|
+
"UnionNode",
|
|
163
167
|
"History",
|
|
164
168
|
]
|
|
@@ -1,24 +1,29 @@
|
|
|
1
|
-
from typing import List, Optional, Sequence
|
|
2
1
|
from collections import defaultdict
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import List, Optional, Sequence
|
|
3
4
|
|
|
5
|
+
from trilogy.core.enums import (
|
|
6
|
+
BooleanOperator,
|
|
7
|
+
Granularity,
|
|
8
|
+
JoinType,
|
|
9
|
+
Purpose,
|
|
10
|
+
PurposeLineage,
|
|
11
|
+
)
|
|
4
12
|
from trilogy.core.models import (
|
|
5
|
-
|
|
6
|
-
QueryDatasource,
|
|
7
|
-
SourceType,
|
|
13
|
+
Comparison,
|
|
8
14
|
Concept,
|
|
9
|
-
|
|
15
|
+
ConceptPair,
|
|
10
16
|
Conditional,
|
|
11
|
-
UnnestJoin,
|
|
12
17
|
Datasource,
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
Environment,
|
|
19
|
+
Grain,
|
|
15
20
|
LooseConceptList,
|
|
16
|
-
|
|
21
|
+
Parenthetical,
|
|
22
|
+
QueryDatasource,
|
|
23
|
+
SourceType,
|
|
24
|
+
UnnestJoin,
|
|
17
25
|
)
|
|
18
|
-
from trilogy.core.enums import Purpose, JoinType, PurposeLineage, Granularity
|
|
19
26
|
from trilogy.utility import unique
|
|
20
|
-
from dataclasses import dataclass
|
|
21
|
-
from trilogy.core.enums import BooleanOperator
|
|
22
27
|
|
|
23
28
|
|
|
24
29
|
def concept_list_to_grain(
|
|
@@ -67,7 +72,6 @@ def resolve_concept_map(
|
|
|
67
72
|
]:
|
|
68
73
|
continue
|
|
69
74
|
if concept.address in full_addresses:
|
|
70
|
-
|
|
71
75
|
concept_map[concept.address].add(input)
|
|
72
76
|
elif concept.address not in concept_map:
|
|
73
77
|
# equi_targets = [x for x in targets if concept.address in x.pseudonyms or x.address in concept.pseudonyms]
|
|
@@ -207,8 +211,22 @@ class StrategyNode:
|
|
|
207
211
|
operator=BooleanOperator.AND,
|
|
208
212
|
)
|
|
209
213
|
self.validate_parents()
|
|
214
|
+
self.validate_inputs()
|
|
210
215
|
self.log = True
|
|
211
216
|
|
|
217
|
+
def validate_inputs(self):
|
|
218
|
+
if not self.parents:
|
|
219
|
+
return
|
|
220
|
+
non_hidden = set()
|
|
221
|
+
for x in self.parents:
|
|
222
|
+
for z in x.usable_outputs:
|
|
223
|
+
non_hidden.add(z.address)
|
|
224
|
+
if not all([x.address in non_hidden for x in self.input_concepts]):
|
|
225
|
+
missing = [x for x in self.input_concepts if x.address not in non_hidden]
|
|
226
|
+
raise ValueError(
|
|
227
|
+
f"Invalid input concepts; {missing} are missing non-hidden parent nodes"
|
|
228
|
+
)
|
|
229
|
+
|
|
212
230
|
def add_parents(self, parents: list["StrategyNode"]):
|
|
213
231
|
self.parents += parents
|
|
214
232
|
self.validate_parents()
|
|
@@ -284,6 +302,14 @@ class StrategyNode:
|
|
|
284
302
|
self.rebuild_cache()
|
|
285
303
|
return self
|
|
286
304
|
|
|
305
|
+
def unhide_output_concepts(self, concepts: List[Concept], rebuild: bool = True):
|
|
306
|
+
self.hidden_concepts = [
|
|
307
|
+
x for x in self.hidden_concepts if x.address not in concepts
|
|
308
|
+
]
|
|
309
|
+
if rebuild:
|
|
310
|
+
self.rebuild_cache()
|
|
311
|
+
return self
|
|
312
|
+
|
|
287
313
|
def remove_output_concepts(self, concepts: List[Concept], rebuild: bool = True):
|
|
288
314
|
for x in concepts:
|
|
289
315
|
self.hidden_concepts.append(x)
|
|
@@ -295,6 +321,12 @@ class StrategyNode:
|
|
|
295
321
|
self.rebuild_cache()
|
|
296
322
|
return self
|
|
297
323
|
|
|
324
|
+
@property
|
|
325
|
+
def usable_outputs(self) -> list[Concept]:
|
|
326
|
+
return [
|
|
327
|
+
x for x in self.output_concepts if x.address not in self.hidden_concepts
|
|
328
|
+
]
|
|
329
|
+
|
|
298
330
|
@property
|
|
299
331
|
def logging_prefix(self) -> str:
|
|
300
332
|
return "\t" * self.depth
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
from trilogy.core.models import (
|
|
5
|
-
|
|
4
|
+
Comparison,
|
|
6
5
|
Concept,
|
|
7
6
|
Conditional,
|
|
8
|
-
Comparison,
|
|
9
|
-
Parenthetical,
|
|
10
7
|
Grain,
|
|
8
|
+
Parenthetical,
|
|
9
|
+
SourceType,
|
|
11
10
|
)
|
|
12
11
|
from trilogy.core.processing.nodes.base_node import StrategyNode
|
|
13
12
|
|
|
@@ -2,25 +2,24 @@ from typing import List, Optional
|
|
|
2
2
|
|
|
3
3
|
from trilogy.constants import logger
|
|
4
4
|
from trilogy.core.models import (
|
|
5
|
-
|
|
6
|
-
QueryDatasource,
|
|
7
|
-
Datasource,
|
|
8
|
-
SourceType,
|
|
5
|
+
Comparison,
|
|
9
6
|
Concept,
|
|
7
|
+
Conditional,
|
|
8
|
+
Datasource,
|
|
10
9
|
Environment,
|
|
10
|
+
Grain,
|
|
11
11
|
LooseConceptList,
|
|
12
|
-
Conditional,
|
|
13
|
-
Comparison,
|
|
14
12
|
Parenthetical,
|
|
13
|
+
QueryDatasource,
|
|
14
|
+
SourceType,
|
|
15
15
|
)
|
|
16
16
|
from trilogy.core.processing.nodes.base_node import (
|
|
17
17
|
StrategyNode,
|
|
18
|
-
resolve_concept_map,
|
|
19
18
|
concept_list_to_grain,
|
|
19
|
+
resolve_concept_map,
|
|
20
20
|
)
|
|
21
|
+
from trilogy.core.processing.utility import find_nullable_concepts, is_scalar_condition
|
|
21
22
|
from trilogy.utility import unique
|
|
22
|
-
from trilogy.core.processing.utility import is_scalar_condition
|
|
23
|
-
from trilogy.core.processing.utility import find_nullable_concepts
|
|
24
23
|
|
|
25
24
|
LOGGER_PREFIX = "[CONCEPT DETAIL - GROUP NODE]"
|
|
26
25
|
|
|
@@ -96,7 +95,6 @@ class GroupNode(StrategyNode):
|
|
|
96
95
|
# otherwise if no group by, just treat it as a select
|
|
97
96
|
source_type = SourceType.SELECT
|
|
98
97
|
else:
|
|
99
|
-
|
|
100
98
|
logger.info(
|
|
101
99
|
f"{self.logging_prefix}{LOGGER_PREFIX} Group node has different grain than parents; forcing group"
|
|
102
100
|
f" upstream grains {[str(source.grain) for source in parent_sources]}"
|
|
@@ -107,9 +105,9 @@ class GroupNode(StrategyNode):
|
|
|
107
105
|
logger.info(
|
|
108
106
|
f"{self.logging_prefix}{LOGGER_PREFIX} Parent node"
|
|
109
107
|
f" {[c.address for c in parent.output_concepts[:2]]}... has"
|
|
110
|
-
" grain"
|
|
108
|
+
" set node grain"
|
|
111
109
|
f" {parent.grain}"
|
|
112
|
-
f" resolved grain {parent.resolve().grain}"
|
|
110
|
+
f" and resolved grain {parent.resolve().grain}"
|
|
113
111
|
f" {type(parent)}"
|
|
114
112
|
)
|
|
115
113
|
source_type = SourceType.GROUP
|
|
@@ -148,7 +146,13 @@ class GroupNode(StrategyNode):
|
|
|
148
146
|
# inject an additional CTE
|
|
149
147
|
if self.conditions and not is_scalar_condition(self.conditions):
|
|
150
148
|
base.condition = None
|
|
151
|
-
base.output_concepts =
|
|
149
|
+
base.output_concepts = unique(
|
|
150
|
+
base.output_concepts + self.conditions.row_arguments, "address"
|
|
151
|
+
)
|
|
152
|
+
# re-visible any hidden concepts
|
|
153
|
+
base.hidden_concepts = [
|
|
154
|
+
x for x in base.hidden_concepts if x not in base.output_concepts
|
|
155
|
+
]
|
|
152
156
|
source_map = resolve_concept_map(
|
|
153
157
|
[base],
|
|
154
158
|
targets=self.output_concepts,
|
|
@@ -1,28 +1,27 @@
|
|
|
1
1
|
from typing import List, Optional, Tuple
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
from trilogy.constants import logger
|
|
5
4
|
from trilogy.core.models import (
|
|
6
5
|
BaseJoin,
|
|
6
|
+
Comparison,
|
|
7
|
+
Concept,
|
|
8
|
+
Conditional,
|
|
9
|
+
Datasource,
|
|
10
|
+
Environment,
|
|
7
11
|
Grain,
|
|
8
12
|
JoinType,
|
|
13
|
+
Parenthetical,
|
|
9
14
|
QueryDatasource,
|
|
10
|
-
Datasource,
|
|
11
15
|
SourceType,
|
|
12
|
-
Concept,
|
|
13
16
|
UnnestJoin,
|
|
14
|
-
Conditional,
|
|
15
|
-
Comparison,
|
|
16
|
-
Parenthetical,
|
|
17
|
-
Environment,
|
|
18
17
|
)
|
|
19
|
-
from trilogy.utility import unique
|
|
20
18
|
from trilogy.core.processing.nodes.base_node import (
|
|
19
|
+
NodeJoin,
|
|
21
20
|
StrategyNode,
|
|
22
21
|
resolve_concept_map,
|
|
23
|
-
NodeJoin,
|
|
24
22
|
)
|
|
25
|
-
from trilogy.core.processing.utility import
|
|
23
|
+
from trilogy.core.processing.utility import find_nullable_concepts, get_node_joins
|
|
24
|
+
from trilogy.utility import unique
|
|
26
25
|
|
|
27
26
|
LOGGER_PREFIX = "[CONCEPT DETAIL - MERGE NODE]"
|
|
28
27
|
|
|
@@ -37,7 +36,8 @@ def deduplicate_nodes(
|
|
|
37
36
|
set_map: dict[str, set[str]] = {}
|
|
38
37
|
for k, v in merged.items():
|
|
39
38
|
unique_outputs = [
|
|
40
|
-
environment.
|
|
39
|
+
# the concept may be a in a different environment for a rowset.
|
|
40
|
+
(environment.concepts.get(x.address) or x).address
|
|
41
41
|
for x in v.output_concepts
|
|
42
42
|
if x not in v.partial_concepts
|
|
43
43
|
]
|
|
@@ -330,8 +330,9 @@ class MergeNode(StrategyNode):
|
|
|
330
330
|
force_group = None
|
|
331
331
|
|
|
332
332
|
qd_joins: List[BaseJoin | UnnestJoin] = [*joins]
|
|
333
|
+
|
|
333
334
|
source_map = resolve_concept_map(
|
|
334
|
-
|
|
335
|
+
final_datasets,
|
|
335
336
|
targets=self.output_concepts,
|
|
336
337
|
inherited_inputs=self.input_concepts + self.existence_concepts,
|
|
337
338
|
full_joins=full_join_concepts,
|
|
@@ -339,6 +340,7 @@ class MergeNode(StrategyNode):
|
|
|
339
340
|
nullable_concepts = find_nullable_concepts(
|
|
340
341
|
source_map=source_map, joins=joins, datasources=final_datasets
|
|
341
342
|
)
|
|
343
|
+
|
|
342
344
|
qds = QueryDatasource(
|
|
343
345
|
input_concepts=unique(self.input_concepts, "address"),
|
|
344
346
|
output_concepts=unique(self.output_concepts, "address"),
|
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
from typing import List, Optional
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
from trilogy.constants import logger
|
|
5
4
|
from trilogy.core.constants import CONSTANT_DATASET
|
|
6
5
|
from trilogy.core.enums import Purpose, PurposeLineage
|
|
7
6
|
from trilogy.core.models import (
|
|
7
|
+
Comparison,
|
|
8
|
+
Concept,
|
|
9
|
+
Conditional,
|
|
10
|
+
Datasource,
|
|
11
|
+
Environment,
|
|
8
12
|
Function,
|
|
9
13
|
Grain,
|
|
14
|
+
Parenthetical,
|
|
10
15
|
QueryDatasource,
|
|
11
16
|
SourceType,
|
|
12
|
-
Concept,
|
|
13
|
-
Environment,
|
|
14
17
|
UnnestJoin,
|
|
15
|
-
Datasource,
|
|
16
|
-
Conditional,
|
|
17
|
-
Comparison,
|
|
18
|
-
Parenthetical,
|
|
19
18
|
)
|
|
20
|
-
from trilogy.utility import unique
|
|
21
19
|
from trilogy.core.processing.nodes.base_node import StrategyNode, resolve_concept_map
|
|
22
|
-
|
|
20
|
+
from trilogy.utility import unique
|
|
23
21
|
|
|
24
22
|
LOGGER_PREFIX = "[CONCEPT DETAIL - SELECT NODE]"
|
|
25
23
|
|
|
@@ -69,6 +67,11 @@ class SelectNode(StrategyNode):
|
|
|
69
67
|
self.accept_partial = accept_partial
|
|
70
68
|
self.datasource = datasource
|
|
71
69
|
|
|
70
|
+
def validate_inputs(self):
|
|
71
|
+
# we do not need to validate inputs for a select node
|
|
72
|
+
# as it will be a root
|
|
73
|
+
return
|
|
74
|
+
|
|
72
75
|
def resolve_from_provided_datasource(
|
|
73
76
|
self,
|
|
74
77
|
) -> QueryDatasource:
|
|
@@ -98,6 +101,7 @@ class SelectNode(StrategyNode):
|
|
|
98
101
|
PurposeLineage.BASIC,
|
|
99
102
|
PurposeLineage.ROWSET,
|
|
100
103
|
PurposeLineage.BASIC,
|
|
104
|
+
PurposeLineage.UNION,
|
|
101
105
|
):
|
|
102
106
|
source_map[x.address] = set()
|
|
103
107
|
|