pytrilogy 0.0.2.47__py3-none-any.whl → 0.0.2.48__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.

Files changed (69) hide show
  1. {pytrilogy-0.0.2.47.dist-info → pytrilogy-0.0.2.48.dist-info}/METADATA +1 -1
  2. pytrilogy-0.0.2.48.dist-info/RECORD +85 -0
  3. trilogy/__init__.py +2 -2
  4. trilogy/constants.py +4 -2
  5. trilogy/core/enums.py +7 -1
  6. trilogy/core/env_processor.py +1 -2
  7. trilogy/core/environment_helpers.py +5 -5
  8. trilogy/core/functions.py +11 -10
  9. trilogy/core/internal.py +2 -3
  10. trilogy/core/models.py +444 -392
  11. trilogy/core/optimization.py +37 -21
  12. trilogy/core/optimizations/__init__.py +1 -1
  13. trilogy/core/optimizations/base_optimization.py +6 -6
  14. trilogy/core/optimizations/inline_constant.py +7 -4
  15. trilogy/core/optimizations/inline_datasource.py +14 -5
  16. trilogy/core/optimizations/predicate_pushdown.py +20 -10
  17. trilogy/core/processing/concept_strategies_v3.py +40 -24
  18. trilogy/core/processing/graph_utils.py +2 -3
  19. trilogy/core/processing/node_generators/__init__.py +7 -5
  20. trilogy/core/processing/node_generators/basic_node.py +4 -4
  21. trilogy/core/processing/node_generators/common.py +10 -11
  22. trilogy/core/processing/node_generators/filter_node.py +7 -9
  23. trilogy/core/processing/node_generators/group_node.py +10 -11
  24. trilogy/core/processing/node_generators/group_to_node.py +5 -5
  25. trilogy/core/processing/node_generators/multiselect_node.py +10 -12
  26. trilogy/core/processing/node_generators/node_merge_node.py +7 -9
  27. trilogy/core/processing/node_generators/rowset_node.py +9 -8
  28. trilogy/core/processing/node_generators/select_merge_node.py +11 -10
  29. trilogy/core/processing/node_generators/select_node.py +5 -5
  30. trilogy/core/processing/node_generators/union_node.py +75 -0
  31. trilogy/core/processing/node_generators/unnest_node.py +2 -3
  32. trilogy/core/processing/node_generators/window_node.py +3 -4
  33. trilogy/core/processing/nodes/__init__.py +9 -5
  34. trilogy/core/processing/nodes/base_node.py +17 -13
  35. trilogy/core/processing/nodes/filter_node.py +3 -4
  36. trilogy/core/processing/nodes/group_node.py +8 -10
  37. trilogy/core/processing/nodes/merge_node.py +11 -11
  38. trilogy/core/processing/nodes/select_node_v2.py +8 -9
  39. trilogy/core/processing/nodes/union_node.py +50 -0
  40. trilogy/core/processing/nodes/unnest_node.py +2 -3
  41. trilogy/core/processing/nodes/window_node.py +2 -3
  42. trilogy/core/processing/utility.py +37 -40
  43. trilogy/core/query_processor.py +68 -44
  44. trilogy/dialect/base.py +95 -53
  45. trilogy/dialect/bigquery.py +2 -3
  46. trilogy/dialect/common.py +5 -4
  47. trilogy/dialect/config.py +0 -2
  48. trilogy/dialect/duckdb.py +2 -2
  49. trilogy/dialect/enums.py +5 -5
  50. trilogy/dialect/postgres.py +2 -2
  51. trilogy/dialect/presto.py +3 -4
  52. trilogy/dialect/snowflake.py +2 -2
  53. trilogy/dialect/sql_server.py +3 -4
  54. trilogy/engine.py +2 -1
  55. trilogy/executor.py +43 -30
  56. trilogy/hooks/base_hook.py +5 -4
  57. trilogy/hooks/graph_hook.py +2 -1
  58. trilogy/hooks/query_debugger.py +18 -8
  59. trilogy/parsing/common.py +15 -20
  60. trilogy/parsing/parse_engine.py +124 -88
  61. trilogy/parsing/render.py +32 -35
  62. trilogy/parsing/trilogy.lark +8 -1
  63. trilogy/scripts/trilogy.py +6 -4
  64. trilogy/utility.py +1 -1
  65. pytrilogy-0.0.2.47.dist-info/RECORD +0 -83
  66. {pytrilogy-0.0.2.47.dist-info → pytrilogy-0.0.2.48.dist-info}/LICENSE.md +0 -0
  67. {pytrilogy-0.0.2.47.dist-info → pytrilogy-0.0.2.48.dist-info}/WHEEL +0 -0
  68. {pytrilogy-0.0.2.47.dist-info → pytrilogy-0.0.2.48.dist-info}/entry_points.txt +0 -0
  69. {pytrilogy-0.0.2.47.dist-info → pytrilogy-0.0.2.48.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 trilogy.core.graph_models import concept_to_node
4
+ from networkx.algorithms import approximation as ax
5
+
7
6
  from trilogy.constants import logger
8
- from trilogy.utility import unique
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 networkx.algorithms import approximation as ax
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
- SelectStatement,
8
+ MultiSelectStatement,
5
9
  RowsetDerivationStatement,
6
10
  RowsetItem,
7
- MultiSelectStatement,
11
+ SelectStatement,
8
12
  WhereClause,
9
13
  )
10
- from trilogy.core.processing.nodes import MergeNode, History, StrategyNode
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,6 +37,7 @@ 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, graph=g, history=history)
41
42
 
42
43
  if not node:
@@ -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 UnnestNode, History, StrategyNode
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 SelectNode, ConstantNode
5
- from .window_node import WindowNode
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 pydantic import BaseModel, Field, ConfigDict
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
- Grain,
6
- QueryDatasource,
7
- SourceType,
13
+ Comparison,
8
14
  Concept,
9
- Environment,
15
+ ConceptPair,
10
16
  Conditional,
11
- UnnestJoin,
12
17
  Datasource,
13
- Comparison,
14
- Parenthetical,
18
+ Environment,
19
+ Grain,
15
20
  LooseConceptList,
16
- ConceptPair,
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]
@@ -1,13 +1,12 @@
1
1
  from typing import List
2
2
 
3
-
4
3
  from trilogy.core.models import (
5
- SourceType,
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
- Grain,
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]}"
@@ -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 get_node_joins, find_nullable_concepts
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.concepts[x.address].address
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
  ]
@@ -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
 
@@ -98,6 +96,7 @@ class SelectNode(StrategyNode):
98
96
  PurposeLineage.BASIC,
99
97
  PurposeLineage.ROWSET,
100
98
  PurposeLineage.BASIC,
99
+ PurposeLineage.UNION,
101
100
  ):
102
101
  source_map[x.address] = set()
103
102
 
@@ -0,0 +1,50 @@
1
+ from typing import List
2
+
3
+ from trilogy.core.models import (
4
+ Concept,
5
+ QueryDatasource,
6
+ SourceType,
7
+ )
8
+ from trilogy.core.processing.nodes.base_node import StrategyNode
9
+
10
+
11
+ class UnionNode(StrategyNode):
12
+ """Union nodes represent combining two keyspaces"""
13
+
14
+ source_type = SourceType.UNION
15
+
16
+ def __init__(
17
+ self,
18
+ input_concepts: List[Concept],
19
+ output_concepts: List[Concept],
20
+ environment,
21
+ g,
22
+ whole_grain: bool = False,
23
+ parents: List["StrategyNode"] | None = None,
24
+ depth: int = 0,
25
+ ):
26
+ super().__init__(
27
+ input_concepts=input_concepts,
28
+ output_concepts=output_concepts,
29
+ environment=environment,
30
+ g=g,
31
+ whole_grain=whole_grain,
32
+ parents=parents,
33
+ depth=depth,
34
+ )
35
+
36
+ def _resolve(self) -> QueryDatasource:
37
+ """We need to ensure that any filtered values are removed from the output to avoid inappropriate references"""
38
+ base = super()._resolve()
39
+ return base
40
+
41
+ def copy(self) -> "UnionNode":
42
+ return UnionNode(
43
+ input_concepts=list(self.input_concepts),
44
+ output_concepts=list(self.output_concepts),
45
+ environment=self.environment,
46
+ g=self.g,
47
+ whole_grain=self.whole_grain,
48
+ parents=self.parents,
49
+ depth=self.depth,
50
+ )
@@ -1,12 +1,11 @@
1
1
  from typing import List
2
2
 
3
-
4
3
  from trilogy.core.models import (
4
+ Concept,
5
+ Function,
5
6
  QueryDatasource,
6
7
  SourceType,
7
- Concept,
8
8
  UnnestJoin,
9
- Function,
10
9
  )
11
10
  from trilogy.core.processing.nodes.base_node import StrategyNode
12
11
 
@@ -1,8 +1,7 @@
1
1
  from typing import List
2
2
 
3
-
4
- from trilogy.core.models import SourceType, Concept
5
- from trilogy.core.processing.nodes.base_node import StrategyNode, QueryDatasource
3
+ from trilogy.core.models import Concept, SourceType
4
+ from trilogy.core.processing.nodes.base_node import QueryDatasource, StrategyNode
6
5
 
7
6
 
8
7
  class WindowNode(StrategyNode):
@@ -1,52 +1,48 @@
1
- from typing import List, Tuple, Dict, Set, Any
1
+ from dataclasses import dataclass
2
+ from enum import Enum
3
+ from logging import Logger
4
+ from typing import Any, Dict, List, Set, Tuple
5
+
2
6
  import networkx as nx
7
+
8
+ from trilogy.core.enums import BooleanOperator, FunctionClass, Granularity, Purpose
3
9
  from trilogy.core.models import (
4
- Datasource,
5
- JoinType,
10
+ CTE,
11
+ AggregateWrapper,
6
12
  BaseJoin,
13
+ CaseElse,
14
+ CaseWhen,
15
+ Comparison,
7
16
  Concept,
8
- QueryDatasource,
9
- LooseConceptList,
10
- Environment,
17
+ ConceptPair,
11
18
  Conditional,
12
- SubselectComparison,
13
- Comparison,
14
- Parenthetical,
15
- Function,
16
- FilterItem,
17
- MagicConstants,
18
- WindowItem,
19
- AggregateWrapper,
19
+ Datasource,
20
20
  DataType,
21
- ConceptPair,
22
- UnnestJoin,
23
- CaseWhen,
24
- CaseElse,
25
- MapWrapper,
26
- ListWrapper,
27
- MapType,
28
21
  DatePart,
29
- NumericType,
22
+ Environment,
23
+ FilterItem,
24
+ Function,
25
+ JoinType,
30
26
  ListType,
31
- TupleWrapper,
32
- CTE,
27
+ ListWrapper,
28
+ LooseConceptList,
29
+ MagicConstants,
30
+ MapType,
31
+ MapWrapper,
33
32
  MultiSelectStatement,
34
- SelectStatement,
33
+ NumericType,
34
+ Parenthetical,
35
35
  ProcessedQuery,
36
+ QueryDatasource,
37
+ SelectStatement,
38
+ SubselectComparison,
39
+ TupleWrapper,
40
+ UnionCTE,
41
+ UnnestJoin,
42
+ WindowItem,
36
43
  )
37
-
38
- from trilogy.core.enums import Purpose, Granularity, BooleanOperator
39
- from enum import Enum
40
44
  from trilogy.utility import unique
41
45
 
42
- from logging import Logger
43
-
44
-
45
- from trilogy.core.enums import FunctionClass
46
-
47
-
48
- from dataclasses import dataclass
49
-
50
46
 
51
47
  class NodeType(Enum):
52
48
  CONCEPT = 1
@@ -314,7 +310,6 @@ def get_node_joins(
314
310
  environment: Environment,
315
311
  # concepts:List[Concept],
316
312
  ):
317
-
318
313
  graph = nx.Graph()
319
314
  partials: dict[str, list[str]] = {}
320
315
  ds_node_map: dict[str, QueryDatasource | Datasource] = {}
@@ -536,7 +531,9 @@ def find_nullable_concepts(
536
531
  return list(sorted(final_nullable))
537
532
 
538
533
 
539
- def sort_select_output_processed(cte: CTE, query: ProcessedQuery) -> CTE:
534
+ def sort_select_output_processed(
535
+ cte: CTE | UnionCTE, query: ProcessedQuery
536
+ ) -> CTE | UnionCTE:
540
537
  hidden_addresses = [c.address for c in query.hidden_columns]
541
538
  output_addresses = [
542
539
  c.address for c in query.output_columns if c.address not in hidden_addresses
@@ -552,8 +549,8 @@ def sort_select_output_processed(cte: CTE, query: ProcessedQuery) -> CTE:
552
549
 
553
550
 
554
551
  def sort_select_output(
555
- cte: CTE, query: SelectStatement | MultiSelectStatement | ProcessedQuery
556
- ) -> CTE:
552
+ cte: CTE | UnionCTE, query: SelectStatement | MultiSelectStatement | ProcessedQuery
553
+ ) -> CTE | UnionCTE:
557
554
  if isinstance(query, ProcessedQuery):
558
555
  return sort_select_output_processed(cte, query)
559
556
  hidden_addresses = [c.address for c in query.hidden_components]