pytrilogy 0.0.2.48__tar.gz → 0.0.2.49__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 (112) hide show
  1. {pytrilogy-0.0.2.48/pytrilogy.egg-info → pytrilogy-0.0.2.49}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/tests/test_query_processing.py +2 -6
  4. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/__init__.py +1 -1
  5. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/models.py +18 -14
  6. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/concept_strategies_v3.py +3 -0
  7. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/node_generators/rowset_node.py +27 -7
  8. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/nodes/base_node.py +28 -0
  9. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/nodes/group_node.py +9 -3
  10. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/nodes/merge_node.py +3 -1
  11. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/nodes/select_node_v2.py +5 -0
  12. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/utility.py +1 -1
  13. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/query_processor.py +3 -7
  14. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/parsing/parse_engine.py +1 -0
  15. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/LICENSE.md +0 -0
  16. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/README.md +0 -0
  17. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/pyproject.toml +0 -0
  18. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/pytrilogy.egg-info/SOURCES.txt +0 -0
  19. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/pytrilogy.egg-info/dependency_links.txt +0 -0
  20. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/pytrilogy.egg-info/entry_points.txt +0 -0
  21. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/pytrilogy.egg-info/requires.txt +0 -0
  22. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/pytrilogy.egg-info/top_level.txt +0 -0
  23. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/setup.cfg +0 -0
  24. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/setup.py +0 -0
  25. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/tests/test_datatypes.py +0 -0
  26. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/tests/test_declarations.py +0 -0
  27. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/tests/test_derived_concepts.py +0 -0
  28. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/tests/test_discovery_nodes.py +0 -0
  29. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/tests/test_enums.py +0 -0
  30. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/tests/test_environment.py +0 -0
  31. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/tests/test_executor.py +0 -0
  32. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/tests/test_functions.py +0 -0
  33. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/tests/test_imports.py +0 -0
  34. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/tests/test_metadata.py +0 -0
  35. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/tests/test_models.py +0 -0
  36. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/tests/test_multi_join_assignments.py +0 -0
  37. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/tests/test_parse_engine.py +0 -0
  38. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/tests/test_parsing.py +0 -0
  39. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/tests/test_partial_handling.py +0 -0
  40. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/tests/test_select.py +0 -0
  41. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/tests/test_show.py +0 -0
  42. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/tests/test_statements.py +0 -0
  43. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/tests/test_undefined_concept.py +0 -0
  44. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/tests/test_where_clause.py +0 -0
  45. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/compiler.py +0 -0
  46. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/constants.py +0 -0
  47. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/__init__.py +0 -0
  48. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/constants.py +0 -0
  49. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/enums.py +0 -0
  50. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/env_processor.py +0 -0
  51. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/environment_helpers.py +0 -0
  52. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/ergonomics.py +0 -0
  53. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/exceptions.py +0 -0
  54. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/functions.py +0 -0
  55. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/graph_models.py +0 -0
  56. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/internal.py +0 -0
  57. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/optimization.py +0 -0
  58. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/optimizations/__init__.py +0 -0
  59. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/optimizations/base_optimization.py +0 -0
  60. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/optimizations/inline_constant.py +0 -0
  61. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/optimizations/inline_datasource.py +0 -0
  62. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  63. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/__init__.py +0 -0
  64. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/graph_utils.py +0 -0
  65. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/node_generators/__init__.py +0 -0
  66. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  67. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/node_generators/common.py +0 -0
  68. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  69. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/node_generators/group_node.py +0 -0
  70. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  71. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  72. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  73. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  74. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/node_generators/select_node.py +0 -0
  75. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/node_generators/union_node.py +0 -0
  76. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  77. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/node_generators/window_node.py +0 -0
  78. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/nodes/__init__.py +0 -0
  79. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/nodes/filter_node.py +0 -0
  80. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/nodes/union_node.py +0 -0
  81. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  82. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/core/processing/nodes/window_node.py +0 -0
  83. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/dialect/__init__.py +0 -0
  84. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/dialect/base.py +0 -0
  85. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/dialect/bigquery.py +0 -0
  86. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/dialect/common.py +0 -0
  87. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/dialect/config.py +0 -0
  88. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/dialect/duckdb.py +0 -0
  89. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/dialect/enums.py +0 -0
  90. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/dialect/postgres.py +0 -0
  91. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/dialect/presto.py +0 -0
  92. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/dialect/snowflake.py +0 -0
  93. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/dialect/sql_server.py +0 -0
  94. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/engine.py +0 -0
  95. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/executor.py +0 -0
  96. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/hooks/__init__.py +0 -0
  97. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/hooks/base_hook.py +0 -0
  98. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/hooks/graph_hook.py +0 -0
  99. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/hooks/query_debugger.py +0 -0
  100. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/metadata/__init__.py +0 -0
  101. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/parser.py +0 -0
  102. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/parsing/__init__.py +0 -0
  103. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/parsing/common.py +0 -0
  104. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/parsing/config.py +0 -0
  105. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/parsing/exceptions.py +0 -0
  106. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/parsing/helpers.py +0 -0
  107. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/parsing/render.py +0 -0
  108. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/parsing/trilogy.lark +0 -0
  109. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/py.typed +0 -0
  110. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/scripts/__init__.py +0 -0
  111. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/scripts/trilogy.py +0 -0
  112. {pytrilogy-0.0.2.48 → pytrilogy-0.0.2.49}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.2.48
3
+ Version: 0.0.2.49
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.2.48
3
+ Version: 0.0.2.49
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -130,9 +130,7 @@ def test_join_aggregate(test_environment: Environment, test_environment_graph):
130
130
 
131
131
  def test_query_aggregation(test_environment, test_environment_graph):
132
132
  select = SelectStatement(selection=[test_environment.concepts["total_revenue"]])
133
- datasource = get_query_datasources(
134
- environment=test_environment, graph=test_environment_graph, statement=select
135
- )
133
+ datasource = get_query_datasources(environment=test_environment, statement=select)
136
134
 
137
135
  assert {datasource.identifier} == {"revenue_at_local_order_id_at_abstract"}
138
136
  check = datasource
@@ -150,9 +148,7 @@ def test_query_datasources(test_environment, test_environment_graph):
150
148
  test_environment.concepts["total_revenue"],
151
149
  ]
152
150
  )
153
- get_query_datasources(
154
- environment=test_environment, graph=test_environment_graph, statement=select
155
- )
151
+ get_query_datasources(environment=test_environment, statement=select)
156
152
 
157
153
 
158
154
  def test_full_query(test_environment, test_environment_graph):
@@ -4,6 +4,6 @@ from trilogy.dialect.enums import Dialects
4
4
  from trilogy.executor import Executor
5
5
  from trilogy.parser import parse
6
6
 
7
- __version__ = "0.0.2.48"
7
+ __version__ = "0.0.2.49"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -467,7 +467,7 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
467
467
  )
468
468
 
469
469
  def __repr__(self):
470
- base = f"{self.namespace}.{self.address}@{self.grain}"
470
+ base = f"{self.address}@{self.grain}"
471
471
  return base
472
472
 
473
473
  @property
@@ -662,11 +662,24 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
662
662
  local_concepts=local_concepts, grain=grain, environment=environment
663
663
  )
664
664
  final_grain = self.grain
665
-
665
+ keys = (
666
+ tuple(
667
+ [
668
+ x.with_select_context(local_concepts, grain, environment)
669
+ for x in self.keys
670
+ ]
671
+ )
672
+ if self.keys
673
+ else None
674
+ )
666
675
  if self.is_aggregate and isinstance(new_lineage, Function):
667
676
  new_lineage = AggregateWrapper(function=new_lineage, by=grain.components)
668
677
  final_grain = grain
669
-
678
+ keys = tuple(grain.components)
679
+ elif (
680
+ self.is_aggregate and not keys and isinstance(new_lineage, AggregateWrapper)
681
+ ):
682
+ keys = tuple(new_lineage.by)
670
683
  return self.__class__(
671
684
  name=self.name,
672
685
  datatype=self.datatype,
@@ -675,16 +688,7 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
675
688
  lineage=new_lineage,
676
689
  grain=final_grain,
677
690
  namespace=self.namespace,
678
- keys=(
679
- tuple(
680
- [
681
- x.with_select_context(local_concepts, grain, environment)
682
- for x in self.keys
683
- ]
684
- )
685
- if self.keys
686
- else None
687
- ),
691
+ keys=keys,
688
692
  modifiers=self.modifiers,
689
693
  # a select needs to always defer to the environment for pseudonyms
690
694
  # TODO: evaluate if this should be cached
@@ -2626,7 +2630,7 @@ class QueryDatasource(BaseModel):
2626
2630
  and CONFIG.validate_missing
2627
2631
  ):
2628
2632
  raise SyntaxError(
2629
- f"Missing source map for {concept.address} on {key}, have {v}"
2633
+ f"On query datasource missing source map for {concept.address} on {key}, have {v}"
2630
2634
  )
2631
2635
  return v
2632
2636
 
@@ -569,6 +569,8 @@ def validate_stack(
569
569
  resolved = node.resolve()
570
570
 
571
571
  for concept in resolved.output_concepts:
572
+ if concept in resolved.hidden_concepts:
573
+ continue
572
574
  validate_concept(
573
575
  concept,
574
576
  node,
@@ -836,6 +838,7 @@ def _search_concepts(
836
838
  PurposeLineage.ROWSET,
837
839
  PurposeLineage.BASIC,
838
840
  PurposeLineage.MULTISELECT,
841
+ PurposeLineage.UNION,
839
842
  ]:
840
843
  skip.add(priority_concept.address)
841
844
  break
@@ -38,7 +38,7 @@ def gen_rowset_node(
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
+ node = get_query_node(environment, select)
42
42
 
43
43
  if not node:
44
44
  logger.info(
@@ -94,15 +94,22 @@ def gen_rowset_node(
94
94
  logger.info(
95
95
  f"{padding(depth)}{LOGGER_PREFIX} no enrichment required for rowset node as all optional found or no optional; exiting early."
96
96
  )
97
- # node.set_preexisting_conditions(conditions.conditional if conditions else None)
98
97
  return node
99
-
100
- possible_joins = concept_to_relevant_joins(node.output_concepts)
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})
101
102
  if not possible_joins:
102
103
  logger.info(
103
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]}"
104
105
  )
105
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])
106
113
  enrich_node: MergeNode = source_concepts( # this fetches the parent + join keys
107
114
  # to then connect to the rest of the query
108
115
  mandatory_list=possible_joins + local_optional,
@@ -110,15 +117,28 @@ def gen_rowset_node(
110
117
  g=g,
111
118
  depth=depth + 1,
112
119
  conditions=conditions,
120
+ history=history,
113
121
  )
114
122
  if not enrich_node:
115
123
  logger.info(
116
124
  f"{padding(depth)}{LOGGER_PREFIX} Cannot generate rowset enrichment node for {concept} with optional {local_optional}, returning just rowset node"
117
125
  )
118
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
+ ]
119
139
  return MergeNode(
120
- input_concepts=enrich_node.output_concepts + node.output_concepts,
121
- output_concepts=node.output_concepts + local_optional,
140
+ input_concepts=non_hidden + non_hidden_enrich,
141
+ output_concepts=non_hidden + local_optional,
122
142
  environment=environment,
123
143
  g=g,
124
144
  depth=depth,
@@ -126,6 +146,6 @@ def gen_rowset_node(
126
146
  node,
127
147
  enrich_node,
128
148
  ],
129
- partial_concepts=node.partial_concepts,
149
+ partial_concepts=node.partial_concepts + enrich_node.partial_concepts,
130
150
  preexisting_conditions=conditions.conditional if conditions else None,
131
151
  )
@@ -211,8 +211,22 @@ class StrategyNode:
211
211
  operator=BooleanOperator.AND,
212
212
  )
213
213
  self.validate_parents()
214
+ self.validate_inputs()
214
215
  self.log = True
215
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
+
216
230
  def add_parents(self, parents: list["StrategyNode"]):
217
231
  self.parents += parents
218
232
  self.validate_parents()
@@ -288,6 +302,14 @@ class StrategyNode:
288
302
  self.rebuild_cache()
289
303
  return self
290
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
+
291
313
  def remove_output_concepts(self, concepts: List[Concept], rebuild: bool = True):
292
314
  for x in concepts:
293
315
  self.hidden_concepts.append(x)
@@ -299,6 +321,12 @@ class StrategyNode:
299
321
  self.rebuild_cache()
300
322
  return self
301
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
+
302
330
  @property
303
331
  def logging_prefix(self) -> str:
304
332
  return "\t" * self.depth
@@ -105,9 +105,9 @@ class GroupNode(StrategyNode):
105
105
  logger.info(
106
106
  f"{self.logging_prefix}{LOGGER_PREFIX} Parent node"
107
107
  f" {[c.address for c in parent.output_concepts[:2]]}... has"
108
- " grain"
108
+ " set node grain"
109
109
  f" {parent.grain}"
110
- f" resolved grain {parent.resolve().grain}"
110
+ f" and resolved grain {parent.resolve().grain}"
111
111
  f" {type(parent)}"
112
112
  )
113
113
  source_type = SourceType.GROUP
@@ -146,7 +146,13 @@ class GroupNode(StrategyNode):
146
146
  # inject an additional CTE
147
147
  if self.conditions and not is_scalar_condition(self.conditions):
148
148
  base.condition = None
149
- base.output_concepts = self.output_concepts + self.conditions.row_arguments
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
+ ]
150
156
  source_map = resolve_concept_map(
151
157
  [base],
152
158
  targets=self.output_concepts,
@@ -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
- list(merged.values()),
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"),
@@ -67,6 +67,11 @@ class SelectNode(StrategyNode):
67
67
  self.accept_partial = accept_partial
68
68
  self.datasource = datasource
69
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
+
70
75
  def resolve_from_provided_datasource(
71
76
  self,
72
77
  ) -> QueryDatasource:
@@ -214,7 +214,7 @@ def concept_to_relevant_joins(concepts: list[Concept]) -> List[Concept]:
214
214
  x for x in concepts if x.keys and all([key in addresses for key in x.keys])
215
215
  ]
216
216
  )
217
- final = [c for c in concepts if c not in sub_props]
217
+ final = [c for c in concepts if c.address not in sub_props]
218
218
  return unique(final, "address")
219
219
 
220
220
 
@@ -7,7 +7,6 @@ from trilogy.core.constants import CONSTANT_DATASET
7
7
  from trilogy.core.enums import BooleanOperator, SourceType
8
8
  from trilogy.core.env_processor import generate_graph
9
9
  from trilogy.core.ergonomics import generate_cte_names
10
- from trilogy.core.graph_models import ReferenceGraph
11
10
  from trilogy.core.models import (
12
11
  CTE,
13
12
  BaseJoin,
@@ -353,13 +352,12 @@ def datasource_to_cte(
353
352
  def get_query_node(
354
353
  environment: Environment,
355
354
  statement: SelectStatement | MultiSelectStatement,
356
- graph: Optional[ReferenceGraph] = None,
357
355
  history: History | None = None,
358
356
  ) -> StrategyNode:
359
357
  environment = environment.duplicate()
360
358
  for k, v in statement.local_concepts.items():
361
359
  environment.concepts[k] = v
362
- graph = graph or generate_graph(environment)
360
+ graph = generate_graph(environment)
363
361
  logger.info(
364
362
  f"{LOGGER_PREFIX} getting source datasource for query with filtering {statement.where_clause_category} and output {[str(c) for c in statement.output_components]}"
365
363
  )
@@ -403,11 +401,10 @@ def get_query_node(
403
401
  def get_query_datasources(
404
402
  environment: Environment,
405
403
  statement: SelectStatement | MultiSelectStatement,
406
- graph: Optional[ReferenceGraph] = None,
407
404
  hooks: Optional[List[BaseHook]] = None,
408
405
  ) -> QueryDatasource:
409
406
 
410
- ds = get_query_node(environment, statement, graph)
407
+ ds = get_query_node(environment, statement)
411
408
  final_qds = ds.resolve()
412
409
  if hooks:
413
410
  for hook in hooks:
@@ -479,10 +476,9 @@ def process_query(
479
476
  hooks: List[BaseHook] | None = None,
480
477
  ) -> ProcessedQuery:
481
478
  hooks = hooks or []
482
- graph = generate_graph(environment)
483
479
 
484
480
  root_datasource = get_query_datasources(
485
- environment=environment, graph=graph, statement=statement, hooks=hooks
481
+ environment=environment, statement=statement, hooks=hooks
486
482
  )
487
483
  for hook in hooks:
488
484
  hook.process_root_datasource(root_datasource)
@@ -570,6 +570,7 @@ class ParseToObjects(Transformer):
570
570
  for new_concept in output.derived_concepts:
571
571
  if new_concept.metadata:
572
572
  new_concept.metadata.line_number = meta.line
573
+ # output.select.local_concepts[new_concept.address] = new_concept
573
574
  self.environment.add_concept(new_concept)
574
575
 
575
576
  return output
File without changes
File without changes
File without changes
File without changes