pytrilogy 0.0.2.58__py3-none-any.whl → 0.0.3.0__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.
- {pytrilogy-0.0.2.58.dist-info → pytrilogy-0.0.3.0.dist-info}/METADATA +9 -2
- pytrilogy-0.0.3.0.dist-info/RECORD +99 -0
- {pytrilogy-0.0.2.58.dist-info → pytrilogy-0.0.3.0.dist-info}/WHEEL +1 -1
- trilogy/__init__.py +2 -2
- trilogy/core/enums.py +1 -7
- trilogy/core/env_processor.py +17 -5
- trilogy/core/environment_helpers.py +11 -25
- trilogy/core/exceptions.py +4 -0
- trilogy/core/functions.py +695 -261
- trilogy/core/graph_models.py +10 -10
- trilogy/core/internal.py +11 -2
- trilogy/core/models/__init__.py +0 -0
- trilogy/core/models/author.py +2110 -0
- trilogy/core/models/build.py +1845 -0
- trilogy/core/models/build_environment.py +151 -0
- trilogy/core/models/core.py +370 -0
- trilogy/core/models/datasource.py +297 -0
- trilogy/core/models/environment.py +696 -0
- trilogy/core/models/execute.py +931 -0
- trilogy/core/optimization.py +14 -16
- trilogy/core/optimizations/base_optimization.py +1 -1
- trilogy/core/optimizations/inline_constant.py +6 -6
- trilogy/core/optimizations/inline_datasource.py +17 -11
- trilogy/core/optimizations/predicate_pushdown.py +17 -16
- trilogy/core/processing/concept_strategies_v3.py +180 -145
- trilogy/core/processing/graph_utils.py +1 -1
- trilogy/core/processing/node_generators/basic_node.py +19 -18
- trilogy/core/processing/node_generators/common.py +50 -44
- trilogy/core/processing/node_generators/filter_node.py +26 -13
- trilogy/core/processing/node_generators/group_node.py +26 -21
- trilogy/core/processing/node_generators/group_to_node.py +11 -8
- trilogy/core/processing/node_generators/multiselect_node.py +60 -43
- trilogy/core/processing/node_generators/node_merge_node.py +76 -38
- trilogy/core/processing/node_generators/rowset_node.py +57 -36
- trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +27 -34
- trilogy/core/processing/node_generators/select_merge_node.py +161 -64
- trilogy/core/processing/node_generators/select_node.py +13 -13
- trilogy/core/processing/node_generators/union_node.py +12 -11
- trilogy/core/processing/node_generators/unnest_node.py +9 -7
- trilogy/core/processing/node_generators/window_node.py +19 -16
- trilogy/core/processing/nodes/__init__.py +21 -18
- trilogy/core/processing/nodes/base_node.py +82 -66
- trilogy/core/processing/nodes/filter_node.py +19 -13
- trilogy/core/processing/nodes/group_node.py +50 -35
- trilogy/core/processing/nodes/merge_node.py +45 -36
- trilogy/core/processing/nodes/select_node_v2.py +53 -39
- trilogy/core/processing/nodes/union_node.py +5 -7
- trilogy/core/processing/nodes/unnest_node.py +7 -11
- trilogy/core/processing/nodes/window_node.py +9 -4
- trilogy/core/processing/utility.py +103 -75
- trilogy/core/query_processor.py +65 -47
- trilogy/core/statements/__init__.py +0 -0
- trilogy/core/statements/author.py +413 -0
- trilogy/core/statements/build.py +0 -0
- trilogy/core/statements/common.py +30 -0
- trilogy/core/statements/execute.py +42 -0
- trilogy/dialect/base.py +146 -106
- trilogy/dialect/common.py +9 -10
- trilogy/dialect/duckdb.py +1 -1
- trilogy/dialect/enums.py +4 -2
- trilogy/dialect/presto.py +1 -1
- trilogy/dialect/sql_server.py +1 -1
- trilogy/executor.py +44 -32
- trilogy/hooks/base_hook.py +6 -4
- trilogy/hooks/query_debugger.py +110 -93
- trilogy/parser.py +1 -1
- trilogy/parsing/common.py +303 -64
- trilogy/parsing/parse_engine.py +263 -617
- trilogy/parsing/render.py +50 -26
- trilogy/scripts/trilogy.py +2 -1
- pytrilogy-0.0.2.58.dist-info/RECORD +0 -87
- trilogy/core/models.py +0 -4960
- {pytrilogy-0.0.2.58.dist-info → pytrilogy-0.0.3.0.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.58.dist-info → pytrilogy-0.0.3.0.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.2.58.dist-info → pytrilogy-0.0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
3
|
from trilogy.constants import logger
|
|
4
|
-
from trilogy.core.models import
|
|
4
|
+
from trilogy.core.models.build import BuildConcept, BuildFunction, BuildWhereClause
|
|
5
|
+
from trilogy.core.models.build_environment import BuildEnvironment
|
|
5
6
|
from trilogy.core.processing.nodes import History, StrategyNode, UnnestNode
|
|
6
7
|
from trilogy.core.processing.utility import padding
|
|
7
8
|
|
|
@@ -9,17 +10,17 @@ LOGGER_PREFIX = "[GEN_UNNEST_NODE]"
|
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def gen_unnest_node(
|
|
12
|
-
concept:
|
|
13
|
-
local_optional: List[
|
|
14
|
-
|
|
13
|
+
concept: BuildConcept,
|
|
14
|
+
local_optional: List[BuildConcept],
|
|
15
|
+
history: History,
|
|
16
|
+
environment: BuildEnvironment,
|
|
15
17
|
g,
|
|
16
18
|
depth: int,
|
|
17
19
|
source_concepts,
|
|
18
|
-
|
|
19
|
-
conditions: WhereClause | None = None,
|
|
20
|
+
conditions: BuildWhereClause | None = None,
|
|
20
21
|
) -> StrategyNode | None:
|
|
21
22
|
arguments = []
|
|
22
|
-
if isinstance(concept.lineage,
|
|
23
|
+
if isinstance(concept.lineage, BuildFunction):
|
|
23
24
|
arguments = concept.lineage.concept_arguments
|
|
24
25
|
|
|
25
26
|
equivalent_optional = [x for x in local_optional if x.lineage == concept.lineage]
|
|
@@ -56,6 +57,7 @@ def gen_unnest_node(
|
|
|
56
57
|
output_concepts=base.output_concepts,
|
|
57
58
|
environment=environment,
|
|
58
59
|
parents=[base],
|
|
60
|
+
# conditions=conditions.conditional if conditions else None,
|
|
59
61
|
preexisting_conditions=conditions.conditional if conditions else None,
|
|
60
62
|
)
|
|
61
63
|
qds = new.resolve()
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
3
|
from trilogy.constants import logger
|
|
4
|
-
from trilogy.core.models import
|
|
4
|
+
from trilogy.core.models.build import BuildConcept, BuildWhereClause, BuildWindowItem
|
|
5
|
+
from trilogy.core.models.build_environment import BuildEnvironment
|
|
5
6
|
from trilogy.core.processing.nodes import History, StrategyNode, WindowNode
|
|
6
7
|
from trilogy.core.processing.utility import padding
|
|
7
8
|
from trilogy.utility import unique
|
|
@@ -9,40 +10,42 @@ from trilogy.utility import unique
|
|
|
9
10
|
LOGGER_PREFIX = "[GEN_WINDOW_NODE]"
|
|
10
11
|
|
|
11
12
|
|
|
13
|
+
WINDOW_TYPES = (BuildWindowItem,)
|
|
14
|
+
|
|
15
|
+
|
|
12
16
|
def resolve_window_parent_concepts(
|
|
13
|
-
concept:
|
|
14
|
-
) -> tuple[
|
|
15
|
-
if not isinstance(concept.lineage,
|
|
17
|
+
concept: BuildConcept, environment: BuildEnvironment
|
|
18
|
+
) -> tuple[BuildConcept, List[BuildConcept]]:
|
|
19
|
+
if not isinstance(concept.lineage, WINDOW_TYPES):
|
|
16
20
|
raise ValueError
|
|
17
21
|
base = []
|
|
22
|
+
logger.info(concept.lineage)
|
|
18
23
|
if concept.lineage.over:
|
|
19
24
|
base += concept.lineage.over
|
|
20
25
|
if concept.lineage.order_by:
|
|
21
26
|
for item in concept.lineage.order_by:
|
|
22
|
-
|
|
23
|
-
# that introduces a circular dependency on an aggregate
|
|
24
|
-
# that is grouped by a window
|
|
25
|
-
# need to figure out how to resolve this
|
|
26
|
-
# base += [environment.concepts[item.expr.output.address]]
|
|
27
|
-
base += [item.expr.output]
|
|
27
|
+
base += item.concept_arguments
|
|
28
28
|
return concept.lineage.content, unique(base, "address")
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def gen_window_node(
|
|
32
|
-
concept:
|
|
33
|
-
local_optional: list[
|
|
34
|
-
environment:
|
|
32
|
+
concept: BuildConcept,
|
|
33
|
+
local_optional: list[BuildConcept],
|
|
34
|
+
environment: BuildEnvironment,
|
|
35
35
|
g,
|
|
36
36
|
depth: int,
|
|
37
37
|
source_concepts,
|
|
38
38
|
history: History | None = None,
|
|
39
|
-
conditions:
|
|
39
|
+
conditions: BuildWhereClause | None = None,
|
|
40
40
|
) -> StrategyNode | None:
|
|
41
41
|
base, parent_concepts = resolve_window_parent_concepts(concept, environment)
|
|
42
|
+
logger.info(
|
|
43
|
+
f"{padding(depth)}{LOGGER_PREFIX} generating window node for {concept} with parents {parent_concepts}"
|
|
44
|
+
)
|
|
42
45
|
equivalent_optional = [
|
|
43
46
|
x
|
|
44
47
|
for x in local_optional
|
|
45
|
-
if isinstance(x.lineage,
|
|
48
|
+
if isinstance(x.lineage, WINDOW_TYPES)
|
|
46
49
|
and resolve_window_parent_concepts(x, environment)[1] == parent_concepts
|
|
47
50
|
]
|
|
48
51
|
|
|
@@ -52,7 +55,7 @@ def gen_window_node(
|
|
|
52
55
|
targets = [base]
|
|
53
56
|
if equivalent_optional:
|
|
54
57
|
for x in equivalent_optional:
|
|
55
|
-
assert isinstance(x.lineage,
|
|
58
|
+
assert isinstance(x.lineage, WINDOW_TYPES)
|
|
56
59
|
targets.append(x.lineage.content)
|
|
57
60
|
|
|
58
61
|
parent_node: StrategyNode = source_concepts(
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from pydantic import BaseModel, ConfigDict, Field
|
|
2
2
|
|
|
3
|
-
from trilogy.core.models import
|
|
3
|
+
from trilogy.core.models.build import BuildConcept, BuildWhereClause
|
|
4
|
+
from trilogy.core.models.build_environment import BuildEnvironment
|
|
5
|
+
from trilogy.core.models.environment import Environment
|
|
4
6
|
|
|
5
7
|
from .base_node import NodeJoin, StrategyNode
|
|
6
8
|
from .filter_node import FilterNode
|
|
@@ -13,6 +15,7 @@ from .window_node import WindowNode
|
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
class History(BaseModel):
|
|
18
|
+
base_environment: Environment
|
|
16
19
|
history: dict[str, StrategyNode | None] = Field(default_factory=dict)
|
|
17
20
|
select_history: dict[str, StrategyNode | None] = Field(default_factory=dict)
|
|
18
21
|
started: set[str] = Field(default_factory=set)
|
|
@@ -20,9 +23,9 @@ class History(BaseModel):
|
|
|
20
23
|
|
|
21
24
|
def _concepts_to_lookup(
|
|
22
25
|
self,
|
|
23
|
-
search: list[
|
|
26
|
+
search: list[BuildConcept],
|
|
24
27
|
accept_partial: bool,
|
|
25
|
-
conditions:
|
|
28
|
+
conditions: BuildWhereClause | None = None,
|
|
26
29
|
) -> str:
|
|
27
30
|
if conditions:
|
|
28
31
|
return (
|
|
@@ -34,10 +37,10 @@ class History(BaseModel):
|
|
|
34
37
|
|
|
35
38
|
def search_to_history(
|
|
36
39
|
self,
|
|
37
|
-
search: list[
|
|
40
|
+
search: list[BuildConcept],
|
|
38
41
|
accept_partial: bool,
|
|
39
42
|
output: StrategyNode | None,
|
|
40
|
-
conditions:
|
|
43
|
+
conditions: BuildWhereClause | None = None,
|
|
41
44
|
):
|
|
42
45
|
self.history[
|
|
43
46
|
self._concepts_to_lookup(search, accept_partial, conditions=conditions)
|
|
@@ -45,8 +48,8 @@ class History(BaseModel):
|
|
|
45
48
|
|
|
46
49
|
def get_history(
|
|
47
50
|
self,
|
|
48
|
-
search: list[
|
|
49
|
-
conditions:
|
|
51
|
+
search: list[BuildConcept],
|
|
52
|
+
conditions: BuildWhereClause | None = None,
|
|
50
53
|
accept_partial: bool = False,
|
|
51
54
|
parent_key: str = "",
|
|
52
55
|
) -> StrategyNode | None | bool:
|
|
@@ -68,9 +71,9 @@ class History(BaseModel):
|
|
|
68
71
|
|
|
69
72
|
def log_start(
|
|
70
73
|
self,
|
|
71
|
-
search: list[
|
|
74
|
+
search: list[BuildConcept],
|
|
72
75
|
accept_partial: bool = False,
|
|
73
|
-
conditions:
|
|
76
|
+
conditions: BuildWhereClause | None = None,
|
|
74
77
|
):
|
|
75
78
|
self.started.add(
|
|
76
79
|
self._concepts_to_lookup(
|
|
@@ -82,9 +85,9 @@ class History(BaseModel):
|
|
|
82
85
|
|
|
83
86
|
def check_started(
|
|
84
87
|
self,
|
|
85
|
-
search: list[
|
|
88
|
+
search: list[BuildConcept],
|
|
86
89
|
accept_partial: bool = False,
|
|
87
|
-
conditions:
|
|
90
|
+
conditions: BuildWhereClause | None = None,
|
|
88
91
|
):
|
|
89
92
|
return (
|
|
90
93
|
self._concepts_to_lookup(
|
|
@@ -97,12 +100,12 @@ class History(BaseModel):
|
|
|
97
100
|
|
|
98
101
|
def _select_concepts_to_lookup(
|
|
99
102
|
self,
|
|
100
|
-
main:
|
|
101
|
-
search: list[
|
|
103
|
+
main: BuildConcept,
|
|
104
|
+
search: list[BuildConcept],
|
|
102
105
|
accept_partial: bool,
|
|
103
106
|
fail_if_not_found: bool,
|
|
104
107
|
accept_partial_optional: bool,
|
|
105
|
-
conditions:
|
|
108
|
+
conditions: BuildWhereClause | None = None,
|
|
106
109
|
) -> str:
|
|
107
110
|
return (
|
|
108
111
|
str(main.address)
|
|
@@ -116,16 +119,16 @@ class History(BaseModel):
|
|
|
116
119
|
|
|
117
120
|
def gen_select_node(
|
|
118
121
|
self,
|
|
119
|
-
concept:
|
|
120
|
-
local_optional: list[
|
|
121
|
-
environment:
|
|
122
|
+
concept: BuildConcept,
|
|
123
|
+
local_optional: list[BuildConcept],
|
|
124
|
+
environment: BuildEnvironment,
|
|
122
125
|
g,
|
|
123
126
|
depth: int,
|
|
124
127
|
source_concepts,
|
|
125
128
|
fail_if_not_found: bool = False,
|
|
126
129
|
accept_partial: bool = False,
|
|
127
130
|
accept_partial_optional: bool = False,
|
|
128
|
-
conditions:
|
|
131
|
+
conditions: BuildWhereClause | None = None,
|
|
129
132
|
) -> StrategyNode | None:
|
|
130
133
|
from trilogy.core.processing.node_generators.select_node import gen_select_node
|
|
131
134
|
|
|
@@ -4,41 +4,40 @@ from typing import List, Optional
|
|
|
4
4
|
|
|
5
5
|
from trilogy.core.enums import (
|
|
6
6
|
BooleanOperator,
|
|
7
|
+
Derivation,
|
|
7
8
|
JoinType,
|
|
8
|
-
PurposeLineage,
|
|
9
|
-
)
|
|
10
|
-
from trilogy.core.models import (
|
|
11
|
-
Comparison,
|
|
12
|
-
Concept,
|
|
13
|
-
ConceptPair,
|
|
14
|
-
Conditional,
|
|
15
|
-
Datasource,
|
|
16
|
-
Environment,
|
|
17
|
-
Grain,
|
|
18
|
-
LooseConceptList,
|
|
19
|
-
Parenthetical,
|
|
20
|
-
QueryDatasource,
|
|
21
9
|
SourceType,
|
|
22
|
-
UnnestJoin,
|
|
23
10
|
)
|
|
11
|
+
from trilogy.core.models.build import (
|
|
12
|
+
BuildComparison,
|
|
13
|
+
BuildConcept,
|
|
14
|
+
BuildConditional,
|
|
15
|
+
BuildDatasource,
|
|
16
|
+
BuildGrain,
|
|
17
|
+
BuildOrderBy,
|
|
18
|
+
BuildParenthetical,
|
|
19
|
+
LooseBuildConceptList,
|
|
20
|
+
)
|
|
21
|
+
from trilogy.core.models.build_environment import BuildEnvironment
|
|
22
|
+
from trilogy.core.models.execute import ConceptPair, QueryDatasource, UnnestJoin
|
|
24
23
|
from trilogy.utility import unique
|
|
25
24
|
|
|
26
25
|
|
|
27
26
|
def resolve_concept_map(
|
|
28
|
-
inputs: List[QueryDatasource |
|
|
29
|
-
targets: List[
|
|
30
|
-
inherited_inputs: List[
|
|
31
|
-
full_joins: List[
|
|
32
|
-
) -> dict[str, set[
|
|
27
|
+
inputs: List[QueryDatasource | BuildDatasource],
|
|
28
|
+
targets: List[BuildConcept],
|
|
29
|
+
inherited_inputs: List[BuildConcept],
|
|
30
|
+
full_joins: List[BuildConcept] | None = None,
|
|
31
|
+
) -> dict[str, set[BuildDatasource | QueryDatasource | UnnestJoin]]:
|
|
33
32
|
targets = targets or []
|
|
34
|
-
concept_map: dict[str, set[
|
|
33
|
+
concept_map: dict[str, set[BuildDatasource | QueryDatasource | UnnestJoin]] = (
|
|
35
34
|
defaultdict(set)
|
|
36
35
|
)
|
|
37
36
|
full_addresses = {c.address for c in full_joins} if full_joins else set()
|
|
38
37
|
inherited = set([t.address for t in inherited_inputs])
|
|
39
38
|
for input in inputs:
|
|
40
39
|
for concept in input.output_concepts:
|
|
41
|
-
if concept.address not in input.
|
|
40
|
+
if concept.address not in input.full_concepts:
|
|
42
41
|
continue
|
|
43
42
|
if (
|
|
44
43
|
isinstance(input, QueryDatasource)
|
|
@@ -76,8 +75,8 @@ def resolve_concept_map(
|
|
|
76
75
|
|
|
77
76
|
|
|
78
77
|
def get_all_parent_partial(
|
|
79
|
-
all_concepts: List[
|
|
80
|
-
) -> List[
|
|
78
|
+
all_concepts: List[BuildConcept], parents: List["StrategyNode"]
|
|
79
|
+
) -> List[BuildConcept]:
|
|
81
80
|
return unique(
|
|
82
81
|
[
|
|
83
82
|
c
|
|
@@ -103,8 +102,8 @@ def get_all_parent_partial(
|
|
|
103
102
|
|
|
104
103
|
|
|
105
104
|
def get_all_parent_nullable(
|
|
106
|
-
all_concepts: List[
|
|
107
|
-
) -> List[
|
|
105
|
+
all_concepts: List[BuildConcept], parents: List["StrategyNode"]
|
|
106
|
+
) -> List[BuildConcept]:
|
|
108
107
|
return unique(
|
|
109
108
|
[
|
|
110
109
|
c
|
|
@@ -127,28 +126,33 @@ class StrategyNode:
|
|
|
127
126
|
|
|
128
127
|
def __init__(
|
|
129
128
|
self,
|
|
130
|
-
input_concepts: List[
|
|
131
|
-
output_concepts: List[
|
|
132
|
-
environment:
|
|
129
|
+
input_concepts: List[BuildConcept],
|
|
130
|
+
output_concepts: List[BuildConcept],
|
|
131
|
+
environment: BuildEnvironment,
|
|
133
132
|
whole_grain: bool = False,
|
|
134
133
|
parents: List["StrategyNode"] | None = None,
|
|
135
|
-
partial_concepts: List[
|
|
136
|
-
nullable_concepts: List[
|
|
134
|
+
partial_concepts: List[BuildConcept] | None = None,
|
|
135
|
+
nullable_concepts: List[BuildConcept] | None = None,
|
|
137
136
|
depth: int = 0,
|
|
138
|
-
conditions:
|
|
139
|
-
|
|
137
|
+
conditions: (
|
|
138
|
+
BuildConditional | BuildComparison | BuildParenthetical | None
|
|
139
|
+
) = None,
|
|
140
|
+
preexisting_conditions: (
|
|
141
|
+
BuildConditional | BuildComparison | BuildParenthetical | None
|
|
142
|
+
) = None,
|
|
140
143
|
force_group: bool | None = None,
|
|
141
|
-
grain: Optional[
|
|
144
|
+
grain: Optional[BuildGrain] = None,
|
|
142
145
|
hidden_concepts: set[str] | None = None,
|
|
143
|
-
existence_concepts: List[
|
|
144
|
-
virtual_output_concepts: List[
|
|
146
|
+
existence_concepts: List[BuildConcept] | None = None,
|
|
147
|
+
virtual_output_concepts: List[BuildConcept] | None = None,
|
|
148
|
+
ordering: BuildOrderBy | None = None,
|
|
145
149
|
):
|
|
146
|
-
self.input_concepts: List[
|
|
150
|
+
self.input_concepts: List[BuildConcept] = (
|
|
147
151
|
unique(input_concepts, "address") if input_concepts else []
|
|
148
152
|
)
|
|
149
|
-
self.input_lcl =
|
|
150
|
-
self.output_concepts: List[
|
|
151
|
-
self.output_lcl =
|
|
153
|
+
self.input_lcl = LooseBuildConceptList(concepts=self.input_concepts)
|
|
154
|
+
self.output_concepts: List[BuildConcept] = unique(output_concepts, "address")
|
|
155
|
+
self.output_lcl = LooseBuildConceptList(concepts=self.output_concepts)
|
|
152
156
|
|
|
153
157
|
self.environment = environment
|
|
154
158
|
self.whole_grain = whole_grain
|
|
@@ -160,7 +164,7 @@ class StrategyNode:
|
|
|
160
164
|
self.nullable_concepts = nullable_concepts or get_all_parent_nullable(
|
|
161
165
|
self.output_concepts, self.parents
|
|
162
166
|
)
|
|
163
|
-
|
|
167
|
+
self.ordering = ordering
|
|
164
168
|
self.depth = depth
|
|
165
169
|
self.conditions = conditions
|
|
166
170
|
self.grain = grain
|
|
@@ -177,7 +181,7 @@ class StrategyNode:
|
|
|
177
181
|
and self.preexisting_conditions
|
|
178
182
|
and self.conditions != self.preexisting_conditions
|
|
179
183
|
):
|
|
180
|
-
self.preexisting_conditions =
|
|
184
|
+
self.preexisting_conditions = BuildConditional(
|
|
181
185
|
left=self.conditions,
|
|
182
186
|
right=self.preexisting_conditions,
|
|
183
187
|
operator=BooleanOperator.AND,
|
|
@@ -207,16 +211,18 @@ class StrategyNode:
|
|
|
207
211
|
return self
|
|
208
212
|
|
|
209
213
|
def set_preexisting_conditions(
|
|
210
|
-
self, conditions:
|
|
214
|
+
self, conditions: BuildConditional | BuildComparison | BuildParenthetical
|
|
211
215
|
):
|
|
212
216
|
self.preexisting_conditions = conditions
|
|
213
217
|
return self
|
|
214
218
|
|
|
215
|
-
def add_condition(
|
|
219
|
+
def add_condition(
|
|
220
|
+
self, condition: BuildConditional | BuildComparison | BuildParenthetical
|
|
221
|
+
):
|
|
216
222
|
if self.conditions and condition == self.conditions:
|
|
217
223
|
return self
|
|
218
224
|
if self.conditions:
|
|
219
|
-
self.conditions =
|
|
225
|
+
self.conditions = BuildConditional(
|
|
220
226
|
left=self.conditions, right=condition, operator=BooleanOperator.AND
|
|
221
227
|
)
|
|
222
228
|
else:
|
|
@@ -238,27 +244,29 @@ class StrategyNode:
|
|
|
238
244
|
self.output_concepts, self.parents
|
|
239
245
|
)
|
|
240
246
|
|
|
241
|
-
self.partial_lcl =
|
|
247
|
+
self.partial_lcl = LooseBuildConceptList(concepts=self.partial_concepts)
|
|
242
248
|
|
|
243
|
-
def add_output_concepts(self, concepts: List[
|
|
249
|
+
def add_output_concepts(self, concepts: List[BuildConcept], rebuild: bool = True):
|
|
244
250
|
for concept in concepts:
|
|
245
251
|
if concept.address not in self.output_lcl.addresses:
|
|
246
252
|
self.output_concepts.append(concept)
|
|
247
|
-
self.output_lcl =
|
|
253
|
+
self.output_lcl = LooseBuildConceptList(concepts=self.output_concepts)
|
|
248
254
|
if rebuild:
|
|
249
255
|
self.rebuild_cache()
|
|
250
256
|
return self
|
|
251
257
|
|
|
252
|
-
def add_partial_concepts(self, concepts: List[
|
|
258
|
+
def add_partial_concepts(self, concepts: List[BuildConcept], rebuild: bool = True):
|
|
253
259
|
for concept in concepts:
|
|
254
260
|
if concept.address not in self.partial_lcl.addresses:
|
|
255
261
|
self.partial_concepts.append(concept)
|
|
256
|
-
self.partial_lcl =
|
|
262
|
+
self.partial_lcl = LooseBuildConceptList(concepts=self.partial_concepts)
|
|
257
263
|
if rebuild:
|
|
258
264
|
self.rebuild_cache()
|
|
259
265
|
return self
|
|
260
266
|
|
|
261
|
-
def add_existence_concepts(
|
|
267
|
+
def add_existence_concepts(
|
|
268
|
+
self, concepts: List[BuildConcept], rebuild: bool = True
|
|
269
|
+
):
|
|
262
270
|
for concept in concepts:
|
|
263
271
|
if concept.address not in self.output_concepts:
|
|
264
272
|
self.existence_concepts.append(concept)
|
|
@@ -266,34 +274,38 @@ class StrategyNode:
|
|
|
266
274
|
self.rebuild_cache()
|
|
267
275
|
return self
|
|
268
276
|
|
|
269
|
-
def set_output_concepts(self, concepts: List[
|
|
277
|
+
def set_output_concepts(self, concepts: List[BuildConcept], rebuild: bool = True):
|
|
270
278
|
# exit if no changes
|
|
271
279
|
if self.output_concepts == concepts:
|
|
272
280
|
return self
|
|
273
281
|
self.output_concepts = concepts
|
|
274
|
-
self.output_lcl =
|
|
282
|
+
self.output_lcl = LooseBuildConceptList(concepts=self.output_concepts)
|
|
275
283
|
|
|
276
284
|
if rebuild:
|
|
277
285
|
self.rebuild_cache()
|
|
278
286
|
return self
|
|
279
287
|
|
|
280
|
-
def add_output_concept(self, concept:
|
|
288
|
+
def add_output_concept(self, concept: BuildConcept, rebuild: bool = True):
|
|
281
289
|
return self.add_output_concepts([concept], rebuild)
|
|
282
290
|
|
|
283
|
-
def hide_output_concepts(self, concepts: List[
|
|
291
|
+
def hide_output_concepts(self, concepts: List[BuildConcept], rebuild: bool = True):
|
|
284
292
|
for x in concepts:
|
|
285
293
|
self.hidden_concepts.add(x.address)
|
|
286
294
|
if rebuild:
|
|
287
295
|
self.rebuild_cache()
|
|
288
296
|
return self
|
|
289
297
|
|
|
290
|
-
def unhide_output_concepts(
|
|
298
|
+
def unhide_output_concepts(
|
|
299
|
+
self, concepts: List[BuildConcept], rebuild: bool = True
|
|
300
|
+
):
|
|
291
301
|
self.hidden_concepts = set(x for x in self.hidden_concepts if x not in concepts)
|
|
292
302
|
if rebuild:
|
|
293
303
|
self.rebuild_cache()
|
|
294
304
|
return self
|
|
295
305
|
|
|
296
|
-
def remove_output_concepts(
|
|
306
|
+
def remove_output_concepts(
|
|
307
|
+
self, concepts: List[BuildConcept], rebuild: bool = True
|
|
308
|
+
):
|
|
297
309
|
for x in concepts:
|
|
298
310
|
self.hidden_concepts.add(x.address)
|
|
299
311
|
addresses = [x.address for x in concepts]
|
|
@@ -305,7 +317,7 @@ class StrategyNode:
|
|
|
305
317
|
return self
|
|
306
318
|
|
|
307
319
|
@property
|
|
308
|
-
def usable_outputs(self) -> list[
|
|
320
|
+
def usable_outputs(self) -> list[BuildConcept]:
|
|
309
321
|
return [
|
|
310
322
|
x for x in self.output_concepts if x.address not in self.hidden_concepts
|
|
311
323
|
]
|
|
@@ -315,11 +327,11 @@ class StrategyNode:
|
|
|
315
327
|
return "\t" * self.depth
|
|
316
328
|
|
|
317
329
|
@property
|
|
318
|
-
def all_concepts(self) -> list[
|
|
330
|
+
def all_concepts(self) -> list[BuildConcept]:
|
|
319
331
|
return [*self.output_concepts]
|
|
320
332
|
|
|
321
333
|
@property
|
|
322
|
-
def all_used_concepts(self) -> list[
|
|
334
|
+
def all_used_concepts(self) -> list[BuildConcept]:
|
|
323
335
|
return [*self.input_concepts, *self.existence_concepts]
|
|
324
336
|
|
|
325
337
|
def __repr__(self):
|
|
@@ -332,11 +344,13 @@ class StrategyNode:
|
|
|
332
344
|
return f"{self.__class__.__name__}<{contents}>"
|
|
333
345
|
|
|
334
346
|
def _resolve(self) -> QueryDatasource:
|
|
335
|
-
parent_sources: List[QueryDatasource |
|
|
347
|
+
parent_sources: List[QueryDatasource | BuildDatasource] = [
|
|
336
348
|
p.resolve() for p in self.parents
|
|
337
349
|
]
|
|
338
350
|
|
|
339
|
-
grain =
|
|
351
|
+
grain = (
|
|
352
|
+
self.grain if self.grain else BuildGrain.from_concepts(self.output_concepts)
|
|
353
|
+
)
|
|
340
354
|
source_map = resolve_concept_map(
|
|
341
355
|
parent_sources,
|
|
342
356
|
targets=self.output_concepts,
|
|
@@ -356,11 +370,12 @@ class StrategyNode:
|
|
|
356
370
|
nullable_concepts=self.nullable_concepts,
|
|
357
371
|
force_group=self.force_group,
|
|
358
372
|
hidden_concepts=self.hidden_concepts,
|
|
373
|
+
ordering=self.ordering,
|
|
359
374
|
)
|
|
360
375
|
|
|
361
376
|
def rebuild_cache(self) -> QueryDatasource:
|
|
362
377
|
self.tainted = True
|
|
363
|
-
self.output_lcl =
|
|
378
|
+
self.output_lcl = LooseBuildConceptList(concepts=self.output_concepts)
|
|
364
379
|
if not self.resolution_cache:
|
|
365
380
|
return self.resolve()
|
|
366
381
|
self.resolution_cache = None
|
|
@@ -390,6 +405,7 @@ class StrategyNode:
|
|
|
390
405
|
hidden_concepts=set(self.hidden_concepts),
|
|
391
406
|
existence_concepts=list(self.existence_concepts),
|
|
392
407
|
virtual_output_concepts=list(self.virtual_output_concepts),
|
|
408
|
+
ordering=self.ordering,
|
|
393
409
|
)
|
|
394
410
|
|
|
395
411
|
|
|
@@ -397,12 +413,14 @@ class StrategyNode:
|
|
|
397
413
|
class NodeJoin:
|
|
398
414
|
left_node: StrategyNode
|
|
399
415
|
right_node: StrategyNode
|
|
400
|
-
concepts: List[
|
|
416
|
+
concepts: List[BuildConcept]
|
|
401
417
|
join_type: JoinType
|
|
402
418
|
filter_to_mutual: bool = False
|
|
403
419
|
concept_pairs: list[ConceptPair] | None = None
|
|
404
420
|
|
|
405
421
|
def __post_init__(self):
|
|
422
|
+
if self.left_node == self.right_node:
|
|
423
|
+
raise SyntaxError("Invalid join, left and right nodes are the same")
|
|
406
424
|
if self.concept_pairs:
|
|
407
425
|
return
|
|
408
426
|
final_concepts = []
|
|
@@ -423,9 +441,7 @@ class NodeJoin:
|
|
|
423
441
|
# if one datasource only has constants
|
|
424
442
|
# we can join on 1=1
|
|
425
443
|
for ds in [self.left_node, self.right_node]:
|
|
426
|
-
if all(
|
|
427
|
-
[c.derivation == PurposeLineage.CONSTANT for c in ds.all_concepts]
|
|
428
|
-
):
|
|
444
|
+
if all([c.derivation == Derivation.CONSTANT for c in ds.all_concepts]):
|
|
429
445
|
self.concepts = []
|
|
430
446
|
return
|
|
431
447
|
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
|
-
from trilogy.core.
|
|
4
|
-
Comparison,
|
|
5
|
-
Concept,
|
|
6
|
-
Conditional,
|
|
7
|
-
Grain,
|
|
8
|
-
Parenthetical,
|
|
3
|
+
from trilogy.core.enums import (
|
|
9
4
|
SourceType,
|
|
10
5
|
)
|
|
6
|
+
from trilogy.core.models.build import (
|
|
7
|
+
BuildComparison,
|
|
8
|
+
BuildConcept,
|
|
9
|
+
BuildConditional,
|
|
10
|
+
BuildGrain,
|
|
11
|
+
BuildParenthetical,
|
|
12
|
+
)
|
|
11
13
|
from trilogy.core.processing.nodes.base_node import StrategyNode
|
|
12
14
|
|
|
13
15
|
|
|
@@ -24,18 +26,22 @@ class FilterNode(StrategyNode):
|
|
|
24
26
|
|
|
25
27
|
def __init__(
|
|
26
28
|
self,
|
|
27
|
-
input_concepts: List[
|
|
28
|
-
output_concepts: List[
|
|
29
|
+
input_concepts: List[BuildConcept],
|
|
30
|
+
output_concepts: List[BuildConcept],
|
|
29
31
|
environment,
|
|
30
32
|
whole_grain: bool = False,
|
|
31
33
|
parents: List["StrategyNode"] | None = None,
|
|
32
34
|
depth: int = 0,
|
|
33
|
-
conditions:
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
conditions: (
|
|
36
|
+
BuildConditional | BuildComparison | BuildParenthetical | None
|
|
37
|
+
) = None,
|
|
38
|
+
preexisting_conditions: (
|
|
39
|
+
BuildConditional | BuildComparison | BuildParenthetical | None
|
|
40
|
+
) = None,
|
|
41
|
+
partial_concepts: List[BuildConcept] | None = None,
|
|
36
42
|
force_group: bool | None = False,
|
|
37
|
-
grain:
|
|
38
|
-
existence_concepts: List[
|
|
43
|
+
grain: BuildGrain | None = None,
|
|
44
|
+
existence_concepts: List[BuildConcept] | None = None,
|
|
39
45
|
):
|
|
40
46
|
super().__init__(
|
|
41
47
|
output_concepts=output_concepts,
|