pytrilogy 0.0.1.108__tar.gz → 0.0.1.110__tar.gz

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 (99) hide show
  1. {pytrilogy-0.0.1.108/pytrilogy.egg-info → pytrilogy-0.0.1.110}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/__init__.py +1 -1
  4. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/constants.py +10 -2
  5. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/enums.py +1 -0
  6. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/models.py +34 -7
  7. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/optimization.py +113 -6
  8. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/node_generators/common.py +1 -0
  9. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/node_generators/rowset_node.py +17 -6
  10. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/nodes/base_node.py +1 -0
  11. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/nodes/merge_node.py +2 -3
  12. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/parsing/parse_engine.py +4 -4
  13. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/LICENSE.md +0 -0
  14. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/README.md +0 -0
  15. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/pyproject.toml +0 -0
  16. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/pytrilogy.egg-info/SOURCES.txt +0 -0
  17. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/pytrilogy.egg-info/dependency_links.txt +0 -0
  18. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/pytrilogy.egg-info/entry_points.txt +0 -0
  19. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/pytrilogy.egg-info/requires.txt +0 -0
  20. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/pytrilogy.egg-info/top_level.txt +0 -0
  21. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/setup.cfg +0 -0
  22. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/setup.py +0 -0
  23. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/tests/test_declarations.py +0 -0
  24. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/tests/test_derived_concepts.py +0 -0
  25. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/tests/test_discovery_nodes.py +0 -0
  26. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/tests/test_environment.py +0 -0
  27. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/tests/test_functions.py +0 -0
  28. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/tests/test_imports.py +0 -0
  29. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/tests/test_metadata.py +0 -0
  30. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/tests/test_models.py +0 -0
  31. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/tests/test_multi_join_assignments.py +0 -0
  32. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/tests/test_parsing.py +0 -0
  33. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/tests/test_partial_handling.py +0 -0
  34. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/tests/test_query_processing.py +0 -0
  35. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/tests/test_select.py +0 -0
  36. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/tests/test_statements.py +0 -0
  37. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/tests/test_undefined_concept.py +0 -0
  38. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/tests/test_where_clause.py +0 -0
  39. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/compiler.py +0 -0
  40. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/__init__.py +0 -0
  41. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/constants.py +0 -0
  42. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/env_processor.py +0 -0
  43. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/environment_helpers.py +0 -0
  44. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/ergonomics.py +0 -0
  45. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/exceptions.py +0 -0
  46. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/functions.py +0 -0
  47. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/graph_models.py +0 -0
  48. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/internal.py +0 -0
  49. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/__init__.py +0 -0
  50. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  51. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/graph_utils.py +0 -0
  52. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/node_generators/__init__.py +0 -0
  53. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  54. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/node_generators/concept_merge_node.py +0 -0
  55. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  56. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/node_generators/group_node.py +0 -0
  57. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  58. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  59. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  60. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/node_generators/select_node.py +0 -0
  61. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  62. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/node_generators/window_node.py +0 -0
  63. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/nodes/__init__.py +0 -0
  64. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/nodes/filter_node.py +0 -0
  65. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/nodes/group_node.py +0 -0
  66. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  67. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  68. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/nodes/window_node.py +0 -0
  69. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/processing/utility.py +0 -0
  70. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/core/query_processor.py +0 -0
  71. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/dialect/__init__.py +0 -0
  72. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/dialect/base.py +0 -0
  73. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/dialect/bigquery.py +0 -0
  74. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/dialect/common.py +0 -0
  75. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/dialect/config.py +0 -0
  76. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/dialect/duckdb.py +0 -0
  77. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/dialect/enums.py +0 -0
  78. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/dialect/postgres.py +0 -0
  79. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/dialect/presto.py +0 -0
  80. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/dialect/snowflake.py +0 -0
  81. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/dialect/sql_server.py +0 -0
  82. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/engine.py +0 -0
  83. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/executor.py +0 -0
  84. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/hooks/__init__.py +0 -0
  85. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/hooks/base_hook.py +0 -0
  86. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/hooks/graph_hook.py +0 -0
  87. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/hooks/query_debugger.py +0 -0
  88. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/metadata/__init__.py +0 -0
  89. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/parser.py +0 -0
  90. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/parsing/__init__.py +0 -0
  91. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/parsing/common.py +0 -0
  92. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/parsing/config.py +0 -0
  93. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/parsing/exceptions.py +0 -0
  94. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/parsing/helpers.py +0 -0
  95. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/parsing/render.py +0 -0
  96. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/py.typed +0 -0
  97. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/scripts/__init__.py +0 -0
  98. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/scripts/trilogy.py +0 -0
  99. {pytrilogy-0.0.1.108 → pytrilogy-0.0.1.110}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.1.108
3
+ Version: 0.0.1.110
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.1.108
3
+ Version: 0.0.1.110
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -4,6 +4,6 @@ from trilogy.executor import Executor
4
4
  from trilogy.parser import parse
5
5
  from trilogy.constants import CONFIG
6
6
 
7
- __version__ = "0.0.1.108"
7
+ __version__ = "0.0.1.110"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -1,8 +1,8 @@
1
1
  from logging import getLogger
2
- from dataclasses import dataclass
2
+ from dataclasses import dataclass, field
3
3
  from enum import Enum
4
4
 
5
- logger = getLogger("preql")
5
+ logger = getLogger("trilogy")
6
6
 
7
7
  DEFAULT_NAMESPACE = "local"
8
8
 
@@ -18,12 +18,20 @@ class MagicConstants(Enum):
18
18
  NULL_VALUE = MagicConstants.NULL
19
19
 
20
20
 
21
+ @dataclass
22
+ class Optimizations:
23
+ predicate_pushdown: bool = True
24
+ datasource_inlining: bool = True
25
+ direct_return: bool = True
26
+
27
+
21
28
  # TODO: support loading from environments
22
29
  @dataclass
23
30
  class Config:
24
31
  strict_mode: bool = True
25
32
  human_identifiers: bool = True
26
33
  inline_datasources: bool = True
34
+ optimizations: Optimizations = field(default_factory=Optimizations)
27
35
 
28
36
 
29
37
  CONFIG = Config()
@@ -263,6 +263,7 @@ class SourceType(Enum):
263
263
  WINDOW = "window"
264
264
  UNNEST = "unnest"
265
265
  CONSTANT = "constant"
266
+ ROWSET = "rowset"
266
267
 
267
268
 
268
269
  class ShowCategory(Enum):
@@ -496,13 +496,31 @@ class Concept(Namespaced, SelectGrain, BaseModel):
496
496
  @property
497
497
  def sources(self) -> List["Concept"]:
498
498
  if self.lineage:
499
- output = []
500
- for item in self.lineage.arguments:
501
- if isinstance(item, Concept):
502
- if item.address == self.address:
503
- raise SyntaxError(f"Concept {self.address} references itself")
504
- output.append(item)
505
- output += item.sources
499
+ output: List[Concept] = []
500
+
501
+ def get_sources(
502
+ expr: Union[
503
+ Function,
504
+ WindowItem,
505
+ FilterItem,
506
+ AggregateWrapper,
507
+ RowsetItem,
508
+ MultiSelectStatement | MergeStatement,
509
+ ],
510
+ output: List[Concept],
511
+ ):
512
+ for item in expr.arguments:
513
+ if isinstance(item, Concept):
514
+ if item.address == self.address:
515
+ raise SyntaxError(
516
+ f"Concept {self.address} references itself"
517
+ )
518
+ output.append(item)
519
+ output += item.sources
520
+ elif isinstance(item, Function):
521
+ get_sources(item, output)
522
+
523
+ get_sources(self.lineage, output)
506
524
  return output
507
525
  return []
508
526
 
@@ -1923,6 +1941,9 @@ class QueryDatasource(BaseModel):
1923
1941
  ),
1924
1942
  join_derived_concepts=self.join_derived_concepts,
1925
1943
  force_group=self.force_group,
1944
+ hidden_concepts=unique(
1945
+ self.hidden_concepts + other.hidden_concepts, "address"
1946
+ ),
1926
1947
  )
1927
1948
 
1928
1949
  return qds
@@ -2083,6 +2104,9 @@ class CTE(BaseModel):
2083
2104
  self.source.output_concepts = unique(
2084
2105
  self.source.output_concepts + other.source.output_concepts, "address"
2085
2106
  )
2107
+ self.hidden_concepts = unique(
2108
+ self.hidden_concepts + other.hidden_concepts, "address"
2109
+ )
2086
2110
  return self
2087
2111
 
2088
2112
  @property
@@ -2710,6 +2734,9 @@ class Comparison(ConceptArgs, Namespaced, SelectGrain, BaseModel):
2710
2734
  def __repr__(self):
2711
2735
  return f"{str(self.left)} {self.operator.value} {str(self.right)}"
2712
2736
 
2737
+ def __str__(self):
2738
+ return self.__repr__()
2739
+
2713
2740
  def with_namespace(self, namespace: str):
2714
2741
  return self.__class__(
2715
2742
  left=(
@@ -4,24 +4,32 @@ from trilogy.core.models import (
4
4
  PersistStatement,
5
5
  Datasource,
6
6
  MultiSelectStatement,
7
+ Conditional,
8
+ BooleanOperator,
7
9
  )
8
10
  from trilogy.core.enums import PurposeLineage
9
- from trilogy.constants import logger
11
+ from trilogy.constants import logger, CONFIG
10
12
  from abc import ABC
11
13
 
12
14
 
15
+ REGISTERED_RULES: list["OptimizationRule"] = []
16
+
17
+
13
18
  class OptimizationRule(ABC):
14
19
 
15
- def optimize(self, cte: CTE) -> bool:
20
+ def optimize(self, cte: CTE, inverse_map: dict[str, list[CTE]]) -> bool:
16
21
  raise NotImplementedError
17
22
 
18
23
  def log(self, message: str):
19
24
  logger.info(f"[Optimization][{self.__class__.__name__}] {message}")
20
25
 
26
+ def debug(self, message: str):
27
+ logger.debug(f"[Optimization][{self.__class__.__name__}] {message}")
28
+
21
29
 
22
30
  class InlineDatasource(OptimizationRule):
23
31
 
24
- def optimize(self, cte: CTE) -> bool:
32
+ def optimize(self, cte: CTE, inverse_map: dict[str, list[CTE]]) -> bool:
25
33
  if not cte.parent_ctes:
26
34
  return False
27
35
 
@@ -60,10 +68,98 @@ class InlineDatasource(OptimizationRule):
60
68
  return optimized
61
69
 
62
70
 
63
- REGISTERED_RULES: list[OptimizationRule] = [InlineDatasource()]
71
+ # This will be used in the future for more complex condition decomposition
72
+ def decompose_condition(conditional: Conditional):
73
+ chunks = []
74
+ if conditional.operator == BooleanOperator.AND:
75
+ for val in [conditional.left, conditional.right]:
76
+ if isinstance(val, Conditional):
77
+ chunks.extend(decompose_condition(val))
78
+ else:
79
+ chunks.append(val)
80
+ else:
81
+ chunks.append(conditional)
82
+ return chunks
83
+
84
+
85
+ def is_child_of(a, comparison):
86
+ if isinstance(comparison, Conditional):
87
+ return (
88
+ is_child_of(a, comparison.left) or is_child_of(a, comparison.right)
89
+ ) and comparison.operator == BooleanOperator.AND
90
+ return comparison == a
91
+
92
+
93
+ class PredicatePushdown(OptimizationRule):
94
+
95
+ def optimize(self, cte: CTE, inverse_map: dict[str, list[CTE]]) -> bool:
96
+
97
+ if not cte.parent_ctes:
98
+ self.debug(f"No parent CTEs for {cte.name}")
99
+
100
+ return False
101
+
102
+ optimized = False
103
+ if not cte.condition:
104
+ self.debug(f"No CTE condition for {cte.name}")
105
+ return False
106
+ self.log(
107
+ f"Checking {cte.name} for predicate pushdown with {len(cte.parent_ctes)} parents"
108
+ )
109
+ if isinstance(cte.condition, Conditional):
110
+ candidates = decompose_condition(cte.condition)
111
+ else:
112
+ candidates = [cte.condition]
113
+ logger.info(f"Have {len(candidates)} candidates to try to push down")
114
+ for candidate in candidates:
115
+ conditions = {x.address for x in candidate.concept_arguments}
116
+ for parent_cte in cte.parent_ctes:
117
+ materialized = {k for k, v in parent_cte.source_map.items() if v != ""}
118
+ if conditions.issubset(materialized):
119
+ if all(
120
+ [
121
+ is_child_of(candidate, child.condition)
122
+ for child in inverse_map[parent_cte.name]
123
+ ]
124
+ ):
125
+ self.log(
126
+ f"All concepts are found on {parent_cte.name} and all it's children include same filter; pushing up filter"
127
+ )
128
+ if parent_cte.condition:
129
+ parent_cte.condition = Conditional(
130
+ left=parent_cte.condition,
131
+ operator=BooleanOperator.AND,
132
+ right=candidate,
133
+ )
134
+ else:
135
+ parent_cte.condition = candidate
136
+ optimized = True
137
+ else:
138
+ logger.info("conditions not subset of parent materialized")
139
+
140
+ if all(
141
+ [
142
+ is_child_of(cte.condition, parent_cte.condition)
143
+ for parent_cte in cte.parent_ctes
144
+ ]
145
+ ):
146
+ self.log("All parents have same filter, removing filter")
147
+ cte.condition = None
148
+ optimized = True
149
+
150
+ return optimized
151
+
152
+
153
+ if CONFIG.optimizations.datasource_inlining:
154
+ REGISTERED_RULES.append(InlineDatasource())
155
+ if CONFIG.optimizations.predicate_pushdown:
156
+ REGISTERED_RULES.append(PredicatePushdown())
64
157
 
65
158
 
66
- def filter_irrelevant_ctes(input: list[CTE], root_cte: CTE):
159
+ def filter_irrelevant_ctes(
160
+ input: list[CTE],
161
+ root_cte: CTE,
162
+ ):
67
163
  relevant_ctes = set()
68
164
 
69
165
  def recurse(cte: CTE):
@@ -75,6 +171,16 @@ def filter_irrelevant_ctes(input: list[CTE], root_cte: CTE):
75
171
  return [cte for cte in input if cte.name in relevant_ctes]
76
172
 
77
173
 
174
+ def gen_inverse_map(input: list[CTE]) -> dict[str, list[CTE]]:
175
+ inverse_map: dict[str, list[CTE]] = {}
176
+ for cte in input:
177
+ for parent in cte.parent_ctes:
178
+ if parent.name not in inverse_map:
179
+ inverse_map[parent.name] = []
180
+ inverse_map[parent.name].append(cte)
181
+ return inverse_map
182
+
183
+
78
184
  def is_direct_return_eligible(
79
185
  cte: CTE, select: SelectStatement | PersistStatement | MultiSelectStatement
80
186
  ) -> bool:
@@ -126,7 +232,8 @@ def optimize_ctes(
126
232
  actions_taken = False
127
233
  for rule in REGISTERED_RULES:
128
234
  for cte in input:
129
- actions_taken = rule.optimize(cte)
235
+ inverse_map = gen_inverse_map(input)
236
+ actions_taken = rule.optimize(cte, inverse_map)
130
237
  complete = not actions_taken
131
238
 
132
239
  if is_direct_return_eligible(root_cte, select):
@@ -196,6 +196,7 @@ def gen_enrichment_node(
196
196
  log_lambda(
197
197
  f"{str(type(base_node).__name__)} returning merge node with group node + enrichment node"
198
198
  )
199
+
199
200
  return MergeNode(
200
201
  input_concepts=unique(
201
202
  join_keys + extra_required + base_node.output_concepts, "address"
@@ -10,9 +10,9 @@ from trilogy.core.processing.nodes import MergeNode, NodeJoin, History, Strategy
10
10
  from trilogy.core.processing.nodes.base_node import concept_list_to_grain
11
11
  from typing import List
12
12
 
13
- from trilogy.core.enums import JoinType
13
+ from trilogy.core.enums import JoinType, PurposeLineage
14
14
  from trilogy.constants import logger
15
- from trilogy.core.processing.utility import padding
15
+ from trilogy.core.processing.utility import padding, unique
16
16
  from trilogy.core.processing.node_generators.common import concept_to_relevant_joins
17
17
 
18
18
 
@@ -40,7 +40,7 @@ def gen_rowset_node(
40
40
  else:
41
41
  targets = select.output_components
42
42
  node: StrategyNode = source_concepts(
43
- mandatory_list=targets,
43
+ mandatory_list=unique(targets, "address"),
44
44
  environment=environment,
45
45
  g=g,
46
46
  depth=depth + 1,
@@ -52,14 +52,19 @@ def gen_rowset_node(
52
52
  )
53
53
  return None
54
54
  node.conditions = select.where_clause.conditional if select.where_clause else None
55
- # rebuild any cached info with the new condition clause
56
- node.rebuild_cache()
57
55
  enrichment = set([x.address for x in local_optional])
58
56
  rowset_relevant = [
59
57
  x
60
58
  for x in rowset.derived_concepts
61
59
  # if x.address == concept.address or x.address in enrichment
62
60
  ]
61
+ select_hidden = set([x.address for x in select.hidden_components])
62
+ rowset_hidden = [
63
+ x
64
+ for x in rowset.derived_concepts
65
+ if isinstance(x.lineage, RowsetItem)
66
+ and x.lineage.content.address in select_hidden
67
+ ]
63
68
  additional_relevant = [
64
69
  x for x in select.output_components if x.address in enrichment
65
70
  ]
@@ -71,9 +76,15 @@ def gen_rowset_node(
71
76
  if select.where_clause:
72
77
  for item in additional_relevant:
73
78
  node.partial_concepts.append(item)
74
-
79
+ node.hidden_concepts = rowset_hidden + [
80
+ x
81
+ for x in node.output_concepts
82
+ if x.address not in [y.address for y in local_optional + [concept]]
83
+ and x.derivation != PurposeLineage.ROWSET
84
+ ]
75
85
  # assume grain to be output of select
76
86
  # but don't include anything aggregate at this point
87
+ node.rebuild_cache()
77
88
  assert node.resolution_cache
78
89
  node.resolution_cache.grain = concept_list_to_grain(
79
90
  node.output_concepts, parent_sources=node.resolution_cache.datasources
@@ -184,6 +184,7 @@ class StrategyNode:
184
184
 
185
185
  def rebuild_cache(self) -> QueryDatasource:
186
186
  self.tainted = True
187
+ self.output_lcl = LooseConceptList(concepts=self.output_concepts)
187
188
  if not self.resolution_cache:
188
189
  return self.resolve()
189
190
  self.resolution_cache = None
@@ -207,6 +207,7 @@ class MergeNode(StrategyNode):
207
207
  joins = self.translate_node_joins(final_joins)
208
208
  else:
209
209
  return []
210
+
210
211
  for join in joins:
211
212
  logger.info(
212
213
  f"{self.logging_prefix}{LOGGER_PREFIX} final join {join.join_type} {[str(c) for c in join.concepts]}"
@@ -281,6 +282,7 @@ class MergeNode(StrategyNode):
281
282
  if c.address in [x.address for x in self.output_concepts]
282
283
  ]
283
284
  )
285
+
284
286
  logger.info(
285
287
  f"{self.logging_prefix}{LOGGER_PREFIX} has pre grain {pregrain} and final merge node grain {grain}"
286
288
  )
@@ -306,9 +308,6 @@ class MergeNode(StrategyNode):
306
308
  f"{self.logging_prefix}{LOGGER_PREFIX} no parents include full grain {grain} and pregrain {pregrain} does not match, assume must group to grain. Have {[str(d.grain) for d in final_datasets]}"
307
309
  )
308
310
  force_group = True
309
- # Grain<returns.customer.id,returns.store.id,returns.item.id,returns.store_sales.ticket_number>
310
- # Grain<returns.customer.id,returns.store.id,returns.return_date.id,returns.item.id,returns.store_sales.ticket_number>
311
- # Grain<returns.customer.id,returns.store.id,returns.item.id,returns.store_sales.ticket_number>
312
311
  else:
313
312
  force_group = None
314
313
 
@@ -981,13 +981,13 @@ class ParseToObjects(Transformer):
981
981
 
982
982
  @v_args(meta=True)
983
983
  def merge_statement(self, meta: Meta, args) -> MergeStatement:
984
-
985
984
  parsed = [self.environment.concepts[x] for x in args]
986
985
  datatypes = {x.datatype for x in parsed}
987
- if not len(datatypes) == 1:
986
+ if not len(datatypes) == 1 and self.environment.concepts.fail_on_missing:
987
+ type_dict = {x.address: x.datatype for x in parsed}
988
988
  raise SyntaxError(
989
- f"Cannot merge concepts with different datatypes {datatypes}"
990
- f"line: {meta.line} concepts: {[x.address for x in parsed]}"
989
+ f"Cannot merge concepts with different datatype"
990
+ f"line: {meta.line} concepts: {type_dict}"
991
991
  )
992
992
  merge = MergeStatement(concepts=parsed, datatype=datatypes.pop())
993
993
  new = merge.merge_concept
File without changes
File without changes
File without changes
File without changes