pytrilogy 0.0.2.17__py3-none-any.whl → 0.0.2.18__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 (40) hide show
  1. {pytrilogy-0.0.2.17.dist-info → pytrilogy-0.0.2.18.dist-info}/METADATA +12 -8
  2. {pytrilogy-0.0.2.17.dist-info → pytrilogy-0.0.2.18.dist-info}/RECORD +40 -39
  3. trilogy/__init__.py +1 -1
  4. trilogy/constants.py +1 -1
  5. trilogy/core/enums.py +1 -0
  6. trilogy/core/functions.py +11 -0
  7. trilogy/core/models.py +89 -47
  8. trilogy/core/optimization.py +15 -9
  9. trilogy/core/processing/concept_strategies_v3.py +372 -145
  10. trilogy/core/processing/node_generators/basic_node.py +27 -55
  11. trilogy/core/processing/node_generators/common.py +6 -7
  12. trilogy/core/processing/node_generators/filter_node.py +28 -31
  13. trilogy/core/processing/node_generators/group_node.py +14 -2
  14. trilogy/core/processing/node_generators/group_to_node.py +3 -1
  15. trilogy/core/processing/node_generators/multiselect_node.py +3 -0
  16. trilogy/core/processing/node_generators/node_merge_node.py +14 -9
  17. trilogy/core/processing/node_generators/rowset_node.py +12 -12
  18. trilogy/core/processing/node_generators/select_merge_node.py +302 -0
  19. trilogy/core/processing/node_generators/select_node.py +7 -511
  20. trilogy/core/processing/node_generators/unnest_node.py +4 -3
  21. trilogy/core/processing/node_generators/window_node.py +12 -37
  22. trilogy/core/processing/nodes/__init__.py +0 -2
  23. trilogy/core/processing/nodes/base_node.py +69 -20
  24. trilogy/core/processing/nodes/filter_node.py +3 -0
  25. trilogy/core/processing/nodes/group_node.py +18 -17
  26. trilogy/core/processing/nodes/merge_node.py +4 -10
  27. trilogy/core/processing/nodes/select_node_v2.py +28 -14
  28. trilogy/core/processing/nodes/window_node.py +1 -2
  29. trilogy/core/processing/utility.py +51 -3
  30. trilogy/core/query_processor.py +17 -73
  31. trilogy/dialect/base.py +7 -3
  32. trilogy/dialect/duckdb.py +4 -1
  33. trilogy/dialect/sql_server.py +3 -3
  34. trilogy/hooks/query_debugger.py +5 -3
  35. trilogy/parsing/parse_engine.py +66 -38
  36. trilogy/parsing/trilogy.lark +2 -1
  37. {pytrilogy-0.0.2.17.dist-info → pytrilogy-0.0.2.18.dist-info}/LICENSE.md +0 -0
  38. {pytrilogy-0.0.2.17.dist-info → pytrilogy-0.0.2.18.dist-info}/WHEEL +0 -0
  39. {pytrilogy-0.0.2.17.dist-info → pytrilogy-0.0.2.18.dist-info}/entry_points.txt +0 -0
  40. {pytrilogy-0.0.2.17.dist-info → pytrilogy-0.0.2.18.dist-info}/top_level.txt +0 -0
@@ -1,17 +1,13 @@
1
1
  # directly select out a basic derivation
2
2
  from typing import List
3
3
 
4
- from trilogy.core.models import (
5
- Concept,
6
- )
4
+ from trilogy.core.models import Concept, WhereClause
7
5
  from trilogy.core.processing.nodes import StrategyNode, History
8
6
  from trilogy.core.processing.node_generators.common import (
9
7
  resolve_function_parent_concepts,
10
8
  )
11
- from trilogy.utility import unique
12
9
  from trilogy.constants import logger
13
10
  from trilogy.core.enums import SourceType
14
- from itertools import combinations
15
11
 
16
12
  LOGGER_PREFIX = "[GEN_BASIC_NODE]"
17
13
 
@@ -24,6 +20,7 @@ def gen_basic_node(
24
20
  depth: int,
25
21
  source_concepts,
26
22
  history: History | None = None,
23
+ conditions: WhereClause | None = None,
27
24
  ):
28
25
  depth_prefix = "\t" * depth
29
26
  parent_concepts = resolve_function_parent_concepts(concept)
@@ -32,10 +29,6 @@ def gen_basic_node(
32
29
  f"{depth_prefix}{LOGGER_PREFIX} basic node for {concept} has parents {[x.address for x in parent_concepts]}"
33
30
  )
34
31
 
35
- local_optional_redundant = [x for x in local_optional if x in parent_concepts]
36
- attempts: List[tuple[list[Concept], list[Concept]]] = [
37
- (parent_concepts, [concept] + local_optional_redundant)
38
- ]
39
32
  equivalent_optional = [
40
33
  x
41
34
  for x in local_optional
@@ -44,56 +37,35 @@ def gen_basic_node(
44
37
  non_equivalent_optional = [
45
38
  x for x in local_optional if x not in equivalent_optional
46
39
  ]
40
+ parent_node: StrategyNode = source_concepts(
41
+ mandatory_list=parent_concepts + non_equivalent_optional,
42
+ environment=environment,
43
+ g=g,
44
+ depth=depth + 1,
45
+ history=history,
46
+ conditions=conditions,
47
+ )
47
48
 
48
- if local_optional:
49
- for combo in range(1, len(non_equivalent_optional) + 1):
50
- combos = combinations(non_equivalent_optional, combo)
51
- for optional_set in combos:
52
- attempts.append(
53
- (
54
- unique(parent_concepts + list(optional_set), "address"),
55
- list(optional_set) + [concept],
56
- )
57
- )
58
- # check for the concept by itself
59
- for attempt, basic_output in reversed(attempts):
60
- partials = []
61
- attempt = unique(attempt, "address")
62
- parent_node: StrategyNode = source_concepts(
63
- mandatory_list=attempt,
64
- environment=environment,
65
- g=g,
66
- depth=depth + 1,
67
- history=history,
49
+ if not parent_node:
50
+ logger.info(
51
+ f"{depth_prefix}{LOGGER_PREFIX} No basic node could be generated for {concept}"
68
52
  )
53
+ return None
69
54
 
70
- if not parent_node:
71
- continue
72
- parent_node.source_type = SourceType.BASIC
73
- parents: List[StrategyNode] = [parent_node]
74
- for x in basic_output:
75
- sources = [p for p in parents if x in p.output_concepts]
76
- if not sources:
77
- continue
78
- if all(x in source.partial_concepts for source in sources):
79
- partials.append(x)
55
+ parent_node.source_type = SourceType.BASIC
56
+ parent_node.add_output_concept(concept)
57
+ for x in equivalent_optional:
58
+ parent_node.add_output_concept(x)
80
59
 
81
- parent_node.add_output_concept(concept)
82
- for x in equivalent_optional:
83
- parent_node.add_output_concept(x)
60
+ parent_node.remove_output_concepts(
61
+ [
62
+ x
63
+ for x in parent_node.output_concepts
64
+ if x.address not in [concept] + local_optional
65
+ ]
66
+ )
84
67
 
85
- parent_node.remove_output_concepts(
86
- [
87
- x
88
- for x in parent_node.output_concepts
89
- if x.address not in [y.address for y in basic_output]
90
- ]
91
- )
92
- logger.info(
93
- f"{depth_prefix}{LOGGER_PREFIX} Returning basic select for {concept} with attempted extra {[x.address for x in attempt]}, output {[x.address for x in parent_node.output_concepts]}"
94
- )
95
- return parent_node
96
68
  logger.info(
97
- f"{depth_prefix}{LOGGER_PREFIX} No basic node could be generated for {concept}"
69
+ f"{depth_prefix}{LOGGER_PREFIX} Returning basic select for {concept}: output {[x.address for x in parent_node.output_concepts]}"
98
70
  )
99
- return None
71
+ return parent_node
@@ -136,6 +136,7 @@ def gen_property_enrichment_node(
136
136
  base_node,
137
137
  ]
138
138
  + final_nodes,
139
+ preexisting_conditions=conditions.conditional if conditions else None,
139
140
  )
140
141
 
141
142
 
@@ -154,11 +155,6 @@ def gen_enrichment_node(
154
155
 
155
156
  local_opts = LooseConceptList(concepts=local_optional)
156
157
 
157
- if local_opts.issubset(base_node.output_lcl):
158
- log_lambda(
159
- f"{str(type(base_node).__name__)} has all optional { base_node.output_lcl}, skipping enrichmennt"
160
- )
161
- return base_node
162
158
  extra_required = [
163
159
  x
164
160
  for x in local_opts
@@ -186,13 +182,15 @@ def gen_enrichment_node(
186
182
  conditions=conditions,
187
183
  log_lambda=log_lambda,
188
184
  )
189
-
185
+ log_lambda(
186
+ f"{str(type(base_node).__name__)} searching for join keys {LooseConceptList(concepts=join_keys)} and extra required {local_opts}"
187
+ )
190
188
  enrich_node: StrategyNode = source_concepts( # this fetches the parent + join keys
191
189
  # to then connect to the rest of the query
192
190
  mandatory_list=join_keys + extra_required,
193
191
  environment=environment,
194
192
  g=g,
195
- depth=depth,
193
+ depth=depth + 1,
196
194
  history=history,
197
195
  conditions=conditions,
198
196
  )
@@ -216,6 +214,7 @@ def gen_enrichment_node(
216
214
  g=g,
217
215
  parents=[enrich_node, base_node],
218
216
  force_group=False,
217
+ preexisting_conditions=conditions.conditional if conditions else None,
219
218
  )
220
219
 
221
220
 
@@ -37,6 +37,7 @@ def gen_filter_node(
37
37
  where = concept.lineage.where
38
38
 
39
39
  optional_included: list[Concept] = []
40
+
40
41
  for x in local_optional:
41
42
  if isinstance(x.lineage, FilterItem):
42
43
  if concept.lineage.where == where:
@@ -45,17 +46,23 @@ def gen_filter_node(
45
46
  )
46
47
  parent_row_concepts.append(x.lineage.content)
47
48
  optional_included.append(x)
49
+ continue
50
+ if conditions and conditions == where:
51
+ optional_included.append(x)
48
52
  logger.info(
49
53
  f"{padding(depth)}{LOGGER_PREFIX} filter {concept.address} derived from {immediate_parent.address} row parents {[x.address for x in parent_row_concepts]} and {[[y.address] for x in parent_existence_concepts for y in x]} existence parents"
50
54
  )
55
+ # we'll populate this with the row parent
56
+ # and the existence parent(s)
51
57
  core_parents = []
58
+
52
59
  row_parent: StrategyNode = source_concepts(
53
60
  mandatory_list=parent_row_concepts,
54
61
  environment=environment,
55
62
  g=g,
56
63
  depth=depth + 1,
57
64
  history=history,
58
- conditions=conditions,
65
+ # conditions=conditions,
59
66
  )
60
67
 
61
68
  flattened_existence = [x for y in parent_existence_concepts for x in y]
@@ -101,6 +108,9 @@ def gen_filter_node(
101
108
  )
102
109
  optimized_pushdown = True
103
110
  if optimized_pushdown:
111
+ logger.info(
112
+ f"{padding(depth)}{LOGGER_PREFIX} returning optimized filter node with pushdown to parent with condition {where.conditional}"
113
+ )
104
114
  if isinstance(row_parent, SelectNode):
105
115
  logger.info(
106
116
  f"{padding(depth)}{LOGGER_PREFIX} nesting select node in strategy node"
@@ -110,16 +120,10 @@ def gen_filter_node(
110
120
  output_concepts=[concept] + row_parent.output_concepts,
111
121
  environment=row_parent.environment,
112
122
  g=row_parent.g,
113
- parents=[row_parent] + core_parents,
123
+ parents=[row_parent],
114
124
  depth=row_parent.depth,
115
125
  partial_concepts=row_parent.partial_concepts,
116
126
  force_group=False,
117
- conditions=(
118
- row_parent.conditions + where.conditional
119
- if row_parent.conditions
120
- else where.conditional
121
- ),
122
- existence_concepts=row_parent.existence_concepts,
123
127
  )
124
128
  else:
125
129
  parent = row_parent
@@ -127,13 +131,14 @@ def gen_filter_node(
127
131
  expected_output = [concept] + [
128
132
  x
129
133
  for x in local_optional
130
- if x.address in [y.address for y in parent.output_concepts]
131
- or x.address in [y.address for y in optional_included]
134
+ if x.address in [y for y in parent.output_concepts]
135
+ or x.address in [y for y in optional_included]
132
136
  ]
133
137
  parent.add_parents(core_parents)
134
138
  parent.add_condition(where.conditional)
135
- parent.add_existence_concepts(flattened_existence)
136
- parent.set_output_concepts(expected_output)
139
+ parent.add_existence_concepts(flattened_existence, False).set_output_concepts(
140
+ expected_output, False
141
+ )
137
142
  parent.grain = Grain(
138
143
  components=(
139
144
  list(immediate_parent.keys)
@@ -147,10 +152,6 @@ def gen_filter_node(
147
152
  ]
148
153
  )
149
154
  parent.rebuild_cache()
150
-
151
- logger.info(
152
- f"{padding(depth)}{LOGGER_PREFIX} returning optimized filter node with pushdown to parent with condition {where.conditional}"
153
- )
154
155
  filter_node = parent
155
156
  else:
156
157
  core_parents.append(row_parent)
@@ -167,26 +168,24 @@ def gen_filter_node(
167
168
  grain=Grain(
168
169
  components=[immediate_parent] + parent_row_concepts,
169
170
  ),
171
+ preexisting_conditions=conditions.conditional if conditions else None,
170
172
  )
171
173
 
172
174
  if not local_optional or all(
173
- [
174
- x.address in [y.address for y in filter_node.output_concepts]
175
- for x in local_optional
176
- ]
175
+ [x.address in filter_node.output_concepts for x in local_optional]
177
176
  ):
178
- outputs = [
179
- x
180
- for x in filter_node.output_concepts
181
- if x.address in [y.address for y in local_optional]
177
+ optional_outputs = [
178
+ x for x in filter_node.output_concepts if x.address in local_optional
182
179
  ]
183
180
  logger.info(
184
- f"{padding(depth)}{LOGGER_PREFIX} no extra enrichment needed for filter node"
181
+ f"{padding(depth)}{LOGGER_PREFIX} no extra enrichment needed for filter node, has all of {[x.address for x in local_optional]}"
182
+ )
183
+ filter_node.set_output_concepts(
184
+ [
185
+ concept,
186
+ ]
187
+ + optional_outputs
185
188
  )
186
- filter_node.output_concepts = [
187
- concept,
188
- ] + outputs
189
- filter_node.rebuild_cache()
190
189
  return filter_node
191
190
 
192
191
  enrich_node = source_concepts( # this fetches the parent + join keys
@@ -198,8 +197,6 @@ def gen_filter_node(
198
197
  history=history,
199
198
  conditions=conditions,
200
199
  )
201
- if not enrich_node:
202
- return filter_node
203
200
  return MergeNode(
204
201
  input_concepts=[concept, immediate_parent] + local_optional,
205
202
  output_concepts=[
@@ -30,7 +30,7 @@ def gen_group_node(
30
30
  source_concepts,
31
31
  history: History | None = None,
32
32
  conditions: WhereClause | None = None,
33
- ):
33
+ ) -> StrategyNode | None:
34
34
  # aggregates MUST always group to the proper grain
35
35
  # except when the
36
36
  parent_concepts: List[Concept] = unique(
@@ -94,12 +94,24 @@ def gen_group_node(
94
94
  g=g,
95
95
  parents=parents,
96
96
  depth=depth,
97
+ preexisting_conditions=conditions.conditional if conditions else None,
97
98
  )
98
99
 
99
100
  # early exit if no optional
101
+
100
102
  if not local_optional:
101
103
  return group_node
102
- logger.info(f"{padding(depth)}{LOGGER_PREFIX} group node requires enrichment")
104
+ missing_optional = [
105
+ x.address for x in local_optional if x.address not in group_node.output_concepts
106
+ ]
107
+ if not missing_optional:
108
+ logger.info(
109
+ f"{padding(depth)}{LOGGER_PREFIX} no extra enrichment needed for group node, has all of {[x.address for x in local_optional]}"
110
+ )
111
+ return group_node
112
+ logger.info(
113
+ f"{padding(depth)}{LOGGER_PREFIX} group node requires enrichment, missing {missing_optional}"
114
+ )
103
115
  return gen_enrichment_node(
104
116
  group_node,
105
117
  join_keys=group_key_parents,
@@ -1,4 +1,4 @@
1
- from trilogy.core.models import Concept, Environment, Function
1
+ from trilogy.core.models import Concept, Environment, Function, WhereClause
2
2
  from trilogy.core.processing.nodes import (
3
3
  GroupNode,
4
4
  StrategyNode,
@@ -21,6 +21,7 @@ def gen_group_to_node(
21
21
  depth: int,
22
22
  source_concepts,
23
23
  history: History | None = None,
24
+ conditions: WhereClause | None = None,
24
25
  ) -> GroupNode | MergeNode:
25
26
  # aggregates MUST always group to the proper grain
26
27
  if not isinstance(concept.lineage, Function):
@@ -36,6 +37,7 @@ def gen_group_to_node(
36
37
  g=g,
37
38
  depth=depth + 1,
38
39
  history=history,
40
+ conditions=conditions,
39
41
  )
40
42
  ]
41
43
 
@@ -2,6 +2,7 @@ from trilogy.core.models import (
2
2
  Concept,
3
3
  Environment,
4
4
  MultiSelectStatement,
5
+ WhereClause,
5
6
  )
6
7
  from trilogy.core.processing.nodes import MergeNode, NodeJoin, History
7
8
  from trilogy.core.processing.nodes.base_node import concept_list_to_grain, StrategyNode
@@ -58,6 +59,7 @@ def gen_multiselect_node(
58
59
  depth: int,
59
60
  source_concepts,
60
61
  history: History | None = None,
62
+ conditions: WhereClause | None = None,
61
63
  ) -> MergeNode | None:
62
64
  if not isinstance(concept.lineage, MultiSelectStatement):
63
65
  logger.info(
@@ -157,6 +159,7 @@ def gen_multiselect_node(
157
159
  g=g,
158
160
  depth=depth + 1,
159
161
  history=history,
162
+ conditions=conditions,
160
163
  )
161
164
  if not enrich_node:
162
165
  logger.info(
@@ -135,7 +135,9 @@ def determine_induced_minimal_nodes(
135
135
  return final
136
136
 
137
137
 
138
- def detect_ambiguity_and_raise(all_concepts, reduced_concept_sets) -> None:
138
+ def detect_ambiguity_and_raise(
139
+ all_concepts: list[Concept], reduced_concept_sets: list[set[str]]
140
+ ) -> None:
139
141
  final_candidates: list[set[str]] = []
140
142
  common: set[str] = set()
141
143
  # find all values that show up in every join_additions
@@ -197,6 +199,9 @@ def resolve_weak_components(
197
199
  for c in all_concepts
198
200
  if "__preql_internal" not in c.address
199
201
  ]
202
+ synonyms: list[Concept] = []
203
+ for x in all_concepts:
204
+ synonyms += x.pseudonyms.values()
200
205
  while break_flag is not True:
201
206
  count += 1
202
207
  if count > AMBIGUITY_CHECK_LIMIT:
@@ -222,13 +227,9 @@ def resolve_weak_components(
222
227
  for node in g.nodes
223
228
  if node.startswith("c~")
224
229
  ]
225
- new = [
226
- x
227
- for x in all_graph_concepts
228
- if x.address not in [y.address for y in all_concepts]
229
- ]
230
+ new = [x for x in all_graph_concepts if x.address not in all_concepts]
230
231
 
231
- new_addresses = set([x.address for x in new])
232
+ new_addresses = set([x.address for x in new if x.address not in synonyms])
232
233
  if not new:
233
234
  break_flag = True
234
235
  # remove our new nodes for the next search path
@@ -299,7 +300,7 @@ def subgraphs_to_merge_node(
299
300
  g=g,
300
301
  depth=depth + 1,
301
302
  history=history,
302
- conditions=search_conditions,
303
+ # conditions=search_conditions,
303
304
  )
304
305
  if not parent:
305
306
  logger.info(
@@ -324,7 +325,9 @@ def subgraphs_to_merge_node(
324
325
  g=g,
325
326
  parents=parents,
326
327
  depth=depth,
327
- conditions=conditions,
328
+ # conditions=conditions,
329
+ # conditions=search_conditions.conditional,
330
+ # preexisting_conditions=search_conditions.conditional,
328
331
  # node_joins=[]
329
332
  )
330
333
 
@@ -341,6 +344,8 @@ def gen_merge_node(
341
344
  search_conditions: WhereClause | None = None,
342
345
  ) -> Optional[MergeNode]:
343
346
 
347
+ if search_conditions:
348
+ all_concepts = unique(all_concepts + search_conditions.row_arguments, "address")
344
349
  for filter_downstream in [True, False]:
345
350
  weak_resolve = resolve_weak_components(
346
351
  all_concepts,
@@ -5,16 +5,15 @@ from trilogy.core.models import (
5
5
  RowsetDerivationStatement,
6
6
  RowsetItem,
7
7
  MultiSelectStatement,
8
+ WhereClause,
8
9
  )
9
10
  from trilogy.core.processing.nodes import MergeNode, History, StrategyNode
10
- from trilogy.core.processing.nodes.base_node import concept_list_to_grain
11
11
  from typing import List
12
12
 
13
13
  from trilogy.core.enums import PurposeLineage
14
14
  from trilogy.constants import logger
15
- from trilogy.core.processing.utility import padding
16
- from trilogy.core.processing.utility import concept_to_relevant_joins
17
-
15
+ from trilogy.core.processing.utility import padding, concept_to_relevant_joins
16
+ from trilogy.core.processing.nodes.base_node import concept_list_to_grain
18
17
 
19
18
  LOGGER_PREFIX = "[GEN_ROWSET_NODE]"
20
19
 
@@ -27,6 +26,7 @@ def gen_rowset_node(
27
26
  depth: int,
28
27
  source_concepts,
29
28
  history: History | None = None,
29
+ conditions: WhereClause | None = None,
30
30
  ) -> StrategyNode | None:
31
31
  from trilogy.core.query_processor import get_query_node
32
32
 
@@ -44,7 +44,6 @@ def gen_rowset_node(
44
44
  f"{padding(depth)}{LOGGER_PREFIX} Cannot generate parent rowset node for {concept}"
45
45
  )
46
46
  return None
47
-
48
47
  enrichment = set([x.address for x in local_optional])
49
48
  rowset_relevant = [x for x in rowset.derived_concepts]
50
49
  select_hidden = set([x.address for x in select.hidden_components])
@@ -67,11 +66,10 @@ def gen_rowset_node(
67
66
  final_hidden = rowset_hidden + [
68
67
  x
69
68
  for x in node.output_concepts
70
- if x.address not in [y.address for y in local_optional + [concept]]
69
+ if x.address not in local_optional + [concept]
71
70
  and x.derivation != PurposeLineage.ROWSET
72
71
  ]
73
72
  node.hide_output_concepts(final_hidden)
74
-
75
73
  assert node.resolution_cache
76
74
  # assume grain to be output of select
77
75
  # but don't include anything hidden(the non-rowset concepts)
@@ -81,9 +79,7 @@ def gen_rowset_node(
81
79
  for x in node.output_concepts
82
80
  if x.address
83
81
  not in [
84
- y.address
85
- for y in node.hidden_concepts
86
- if y.derivation != PurposeLineage.ROWSET
82
+ y for y in node.hidden_concepts if y.derivation != PurposeLineage.ROWSET
87
83
  ]
88
84
  ],
89
85
  parent_sources=node.resolution_cache.datasources,
@@ -91,14 +87,16 @@ def gen_rowset_node(
91
87
 
92
88
  node.rebuild_cache()
93
89
 
94
- possible_joins = concept_to_relevant_joins(additional_relevant)
95
90
  if not local_optional or all(
96
- x.address in [y.address for y in node.output_concepts] for x in local_optional
91
+ x.address in node.output_concepts for x in local_optional
97
92
  ):
98
93
  logger.info(
99
94
  f"{padding(depth)}{LOGGER_PREFIX} no enrichment required for rowset node as all optional found or no optional; exiting early."
100
95
  )
96
+ # node.set_preexisting_conditions(conditions.conditional if conditions else None)
101
97
  return node
98
+
99
+ possible_joins = concept_to_relevant_joins(additional_relevant)
102
100
  if not possible_joins:
103
101
  logger.info(
104
102
  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]}"
@@ -110,6 +108,7 @@ def gen_rowset_node(
110
108
  environment=environment,
111
109
  g=g,
112
110
  depth=depth + 1,
111
+ conditions=conditions,
113
112
  )
114
113
  if not enrich_node:
115
114
  logger.info(
@@ -127,4 +126,5 @@ def gen_rowset_node(
127
126
  enrich_node,
128
127
  ],
129
128
  partial_concepts=node.partial_concepts,
129
+ preexisting_conditions=conditions.conditional if conditions else None,
130
130
  )