pytrilogy 0.0.2.28__tar.gz → 0.0.2.29__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 (108) hide show
  1. {pytrilogy-0.0.2.28/pytrilogy.egg-info → pytrilogy-0.0.2.29}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/tests/test_parsing.py +30 -0
  4. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/__init__.py +1 -1
  5. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/models.py +0 -1
  6. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/optimization.py +1 -15
  7. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/concept_strategies_v3.py +2 -3
  8. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/select_merge_node.py +16 -2
  9. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/nodes/base_node.py +0 -3
  10. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/nodes/select_node_v2.py +1 -4
  11. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/utility.py +38 -0
  12. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/dialect/base.py +13 -9
  13. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/LICENSE.md +0 -0
  14. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/README.md +0 -0
  15. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/pyproject.toml +0 -0
  16. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/pytrilogy.egg-info/SOURCES.txt +0 -0
  17. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/pytrilogy.egg-info/dependency_links.txt +0 -0
  18. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/pytrilogy.egg-info/entry_points.txt +0 -0
  19. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/pytrilogy.egg-info/requires.txt +0 -0
  20. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/pytrilogy.egg-info/top_level.txt +0 -0
  21. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/setup.cfg +0 -0
  22. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/setup.py +0 -0
  23. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/tests/test_datatypes.py +0 -0
  24. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/tests/test_declarations.py +0 -0
  25. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/tests/test_derived_concepts.py +0 -0
  26. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/tests/test_discovery_nodes.py +0 -0
  27. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/tests/test_environment.py +0 -0
  28. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/tests/test_executor.py +0 -0
  29. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/tests/test_functions.py +0 -0
  30. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/tests/test_imports.py +0 -0
  31. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/tests/test_metadata.py +0 -0
  32. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/tests/test_models.py +0 -0
  33. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/tests/test_multi_join_assignments.py +0 -0
  34. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/tests/test_partial_handling.py +0 -0
  35. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/tests/test_query_processing.py +0 -0
  36. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/tests/test_select.py +0 -0
  37. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/tests/test_show.py +0 -0
  38. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/tests/test_statements.py +0 -0
  39. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/tests/test_undefined_concept.py +0 -0
  40. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/tests/test_where_clause.py +0 -0
  41. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/compiler.py +0 -0
  42. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/constants.py +0 -0
  43. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/__init__.py +0 -0
  44. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/constants.py +0 -0
  45. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/enums.py +0 -0
  46. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/env_processor.py +0 -0
  47. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/environment_helpers.py +0 -0
  48. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/ergonomics.py +0 -0
  49. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/exceptions.py +0 -0
  50. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/functions.py +0 -0
  51. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/graph_models.py +0 -0
  52. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/internal.py +0 -0
  53. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/optimizations/__init__.py +0 -0
  54. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/optimizations/base_optimization.py +0 -0
  55. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/optimizations/inline_constant.py +0 -0
  56. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/optimizations/inline_datasource.py +0 -0
  57. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  58. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/__init__.py +0 -0
  59. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/graph_utils.py +0 -0
  60. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/__init__.py +0 -0
  61. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  62. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/common.py +0 -0
  63. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  64. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/group_node.py +0 -0
  65. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  66. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  67. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  68. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  69. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/select_node.py +0 -0
  70. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  71. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/node_generators/window_node.py +0 -0
  72. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/nodes/__init__.py +0 -0
  73. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/nodes/filter_node.py +0 -0
  74. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/nodes/group_node.py +0 -0
  75. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/nodes/merge_node.py +0 -0
  76. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  77. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/processing/nodes/window_node.py +0 -0
  78. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/core/query_processor.py +0 -0
  79. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/dialect/__init__.py +0 -0
  80. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/dialect/bigquery.py +0 -0
  81. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/dialect/common.py +0 -0
  82. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/dialect/config.py +0 -0
  83. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/dialect/duckdb.py +0 -0
  84. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/dialect/enums.py +0 -0
  85. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/dialect/postgres.py +0 -0
  86. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/dialect/presto.py +0 -0
  87. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/dialect/snowflake.py +0 -0
  88. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/dialect/sql_server.py +0 -0
  89. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/engine.py +0 -0
  90. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/executor.py +0 -0
  91. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/hooks/__init__.py +0 -0
  92. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/hooks/base_hook.py +0 -0
  93. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/hooks/graph_hook.py +0 -0
  94. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/hooks/query_debugger.py +0 -0
  95. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/metadata/__init__.py +0 -0
  96. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/parser.py +0 -0
  97. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/parsing/__init__.py +0 -0
  98. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/parsing/common.py +0 -0
  99. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/parsing/config.py +0 -0
  100. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/parsing/exceptions.py +0 -0
  101. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/parsing/helpers.py +0 -0
  102. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/parsing/parse_engine.py +0 -0
  103. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/parsing/render.py +0 -0
  104. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/parsing/trilogy.lark +0 -0
  105. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/py.typed +0 -0
  106. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/scripts/__init__.py +0 -0
  107. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/scripts/trilogy.py +0 -0
  108. {pytrilogy-0.0.2.28 → pytrilogy-0.0.2.29}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.2.28
3
+ Version: 0.0.2.29
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.28
3
+ Version: 0.0.2.29
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -7,6 +7,7 @@ from trilogy.core.models import (
7
7
  Environment,
8
8
  Comparison,
9
9
  TupleWrapper,
10
+ Datasource,
10
11
  )
11
12
  from trilogy.core.functions import argument_to_purpose, function_args_to_output_purpose
12
13
  from trilogy.parsing.parse_engine import (
@@ -511,6 +512,35 @@ address `abc:def`
511
512
  assert ds.non_partial_for.conditional.right == 10
512
513
 
513
514
 
515
+ def test_datasource_from_persist():
516
+
517
+ text = """
518
+ key x int;
519
+ key y int;
520
+
521
+ datasource test (
522
+ x:x,
523
+ y:y)
524
+ grain(x)
525
+ address `abc:def`
526
+ ;
527
+
528
+ persist alias into tbl_alias from
529
+ select
530
+ x,
531
+ y
532
+ where y>10;
533
+
534
+
535
+
536
+ """
537
+ env, parsed = parse_text(text)
538
+
539
+ ds: Datasource = parsed[-1].datasource
540
+ assert ds.non_partial_for.conditional.right == 10
541
+ assert not ds.where
542
+
543
+
514
544
  def test_filter_concise():
515
545
 
516
546
  text = """
@@ -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.2.28"
7
+ __version__ = "0.0.2.29"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -1760,7 +1760,6 @@ class SelectStatement(HasUUID, Mergeable, Namespaced, SelectTypeMixin, BaseModel
1760
1760
  grain=grain or self.grain,
1761
1761
  columns=columns,
1762
1762
  namespace=namespace,
1763
- where=WhereClause(conditional=condition) if condition else None,
1764
1763
  non_partial_for=WhereClause(conditional=condition) if condition else None,
1765
1764
  )
1766
1765
  for column in columns:
@@ -13,7 +13,7 @@ from trilogy.core.optimizations import (
13
13
  PredicatePushdownRemove,
14
14
  InlineDatasource,
15
15
  )
16
-
16
+ from trilogy.core.processing.utility import sort_select_output
17
17
 
18
18
  MAX_OPTIMIZATION_LOOPS = 100
19
19
 
@@ -154,20 +154,6 @@ def is_direct_return_eligible(cte: CTE) -> CTE | None:
154
154
  return direct_parent
155
155
 
156
156
 
157
- def sort_select_output(cte: CTE, query: SelectStatement | MultiSelectStatement):
158
- hidden_addresses = [c.address for c in query.hidden_components]
159
- output_addresses = [
160
- c.address for c in query.output_components if c.address not in hidden_addresses
161
- ]
162
-
163
- mapping = {x.address: x for x in cte.output_columns}
164
-
165
- new_output = []
166
- for x in output_addresses:
167
- new_output.append(mapping[x])
168
- cte.output_columns = new_output
169
-
170
-
171
157
  def optimize_ctes(
172
158
  input: list[CTE], root_cte: CTE, select: SelectStatement | MultiSelectStatement
173
159
  ) -> list[CTE]:
@@ -838,9 +838,8 @@ def _search_concepts(
838
838
  f" {accept_partial} (complete: {complete}), have {found} from {[n for n in stack]} (missing {missing} partial {partial} virtual {virtual}), attempted {attempted}, mandatory w/ filter {mandatory_completion}"
839
839
  )
840
840
  if complete == ValidationResult.INCOMPLETE_CONDITION:
841
- raise SyntaxError(
842
- {str(node): node.preexisting_conditions for node in stack}
843
- )
841
+ cond_dict = {str(node): node.preexisting_conditions for node in stack}
842
+ raise SyntaxError(f"Have {cond_dict} and need {str(conditions)}")
844
843
  # early exit if we have a complete stack with one node
845
844
  # we can only early exit if we have a complete stack
846
845
  # and we are not looking for more non-partial sources
@@ -233,7 +233,6 @@ def create_select_node(
233
233
  ]
234
234
  nullable_lcl = LooseConceptList(concepts=nullable_concepts)
235
235
  partial_is_full = conditions and (conditions == datasource.non_partial_for)
236
-
237
236
  bcandidate: StrategyNode = SelectNode(
238
237
  input_concepts=[c.concept for c in datasource.columns],
239
238
  output_concepts=all_concepts,
@@ -249,7 +248,9 @@ def create_select_node(
249
248
  datasource=datasource,
250
249
  grain=Grain(components=all_concepts),
251
250
  conditions=datasource.where.conditional if datasource.where else None,
252
- render_condition=not partial_is_full,
251
+ preexisting_conditions=(
252
+ conditions.conditional if partial_is_full and conditions else None
253
+ ),
253
254
  )
254
255
 
255
256
  # we need to nest the group node one further
@@ -263,6 +264,9 @@ def create_select_node(
263
264
  depth=depth,
264
265
  partial_concepts=bcandidate.partial_concepts,
265
266
  nullable_concepts=bcandidate.nullable_concepts,
267
+ preexisting_conditions=(
268
+ conditions.conditional if partial_is_full and conditions else None
269
+ ),
266
270
  )
267
271
  else:
268
272
  candidate = bcandidate
@@ -340,6 +344,15 @@ def gen_select_merge_node(
340
344
 
341
345
  if len(parents) == 1:
342
346
  return parents[0]
347
+ preexisting_conditions = None
348
+ if conditions and all(
349
+ [
350
+ x.preexisting_conditions
351
+ and x.preexisting_conditions == conditions.conditional
352
+ for x in parents
353
+ ]
354
+ ):
355
+ preexisting_conditions = conditions.conditional
343
356
  return MergeNode(
344
357
  output_concepts=all_concepts,
345
358
  input_concepts=non_constant,
@@ -347,4 +360,5 @@ def gen_select_merge_node(
347
360
  g=g,
348
361
  depth=depth,
349
362
  parents=parents,
363
+ preexisting_conditions=preexisting_conditions,
350
364
  )
@@ -165,7 +165,6 @@ class StrategyNode:
165
165
  hidden_concepts: List[Concept] | None = None,
166
166
  existence_concepts: List[Concept] | None = None,
167
167
  virtual_output_concepts: List[Concept] | None = None,
168
- render_condition: bool = True,
169
168
  ):
170
169
  self.input_concepts: List[Concept] = (
171
170
  unique(input_concepts, "address") if input_concepts else []
@@ -209,7 +208,6 @@ class StrategyNode:
209
208
  )
210
209
  self.validate_parents()
211
210
  self.log = True
212
- self.render_condition = render_condition
213
211
 
214
212
  def add_parents(self, parents: list["StrategyNode"]):
215
213
  self.parents += parents
@@ -382,7 +380,6 @@ class StrategyNode:
382
380
  hidden_concepts=list(self.hidden_concepts),
383
381
  existence_concepts=list(self.existence_concepts),
384
382
  virtual_output_concepts=list(self.virtual_output_concepts),
385
- render_condition=self.render_condition,
386
383
  )
387
384
 
388
385
 
@@ -49,7 +49,6 @@ class SelectNode(StrategyNode):
49
49
  conditions: Conditional | Comparison | Parenthetical | None = None,
50
50
  preexisting_conditions: Conditional | Comparison | Parenthetical | None = None,
51
51
  hidden_concepts: List[Concept] | None = None,
52
- render_condition: bool = True,
53
52
  ):
54
53
  super().__init__(
55
54
  input_concepts=input_concepts,
@@ -66,7 +65,6 @@ class SelectNode(StrategyNode):
66
65
  conditions=conditions,
67
66
  preexisting_conditions=preexisting_conditions,
68
67
  hidden_concepts=hidden_concepts,
69
- render_condition=render_condition,
70
68
  )
71
69
  self.accept_partial = accept_partial
72
70
  self.datasource = datasource
@@ -123,7 +121,7 @@ class SelectNode(StrategyNode):
123
121
  nullable_concepts=[c.concept for c in datasource.columns if c.is_nullable],
124
122
  source_type=SourceType.DIRECT_SELECT,
125
123
  # we can skip rendering conditions
126
- condition=self.conditions if self.render_condition else None,
124
+ condition=self.conditions,
127
125
  # select nodes should never group
128
126
  force_group=self.force_group,
129
127
  hidden_concepts=self.hidden_concepts,
@@ -208,7 +206,6 @@ class SelectNode(StrategyNode):
208
206
  conditions=self.conditions,
209
207
  preexisting_conditions=self.preexisting_conditions,
210
208
  hidden_concepts=self.hidden_concepts,
211
- render_condition=self.render_condition,
212
209
  )
213
210
 
214
211
 
@@ -29,6 +29,10 @@ from trilogy.core.models import (
29
29
  NumericType,
30
30
  ListType,
31
31
  TupleWrapper,
32
+ CTE,
33
+ MultiSelectStatement,
34
+ SelectStatement,
35
+ ProcessedQuery,
32
36
  )
33
37
 
34
38
  from trilogy.core.enums import Purpose, Granularity, BooleanOperator
@@ -528,3 +532,37 @@ def find_nullable_concepts(
528
532
  if set(v).issubset(all_ds):
529
533
  final_nullable.add(k)
530
534
  return list(sorted(final_nullable))
535
+
536
+
537
+ def sort_select_output_processed(cte: CTE, query: ProcessedQuery) -> CTE:
538
+ hidden_addresses = [c.address for c in query.hidden_columns]
539
+ output_addresses = [
540
+ c.address for c in query.output_columns if c.address not in hidden_addresses
541
+ ]
542
+
543
+ mapping = {x.address: x for x in cte.output_columns}
544
+
545
+ new_output = []
546
+ for x in output_addresses:
547
+ new_output.append(mapping[x])
548
+ cte.output_columns = new_output
549
+ return cte
550
+
551
+
552
+ def sort_select_output(
553
+ cte: CTE, query: SelectStatement | MultiSelectStatement | ProcessedQuery
554
+ ) -> CTE:
555
+ if isinstance(query, ProcessedQuery):
556
+ return sort_select_output_processed(cte, query)
557
+ hidden_addresses = [c.address for c in query.hidden_components]
558
+ output_addresses = [
559
+ c.address for c in query.output_components if c.address not in hidden_addresses
560
+ ]
561
+
562
+ mapping = {x.address: x for x in cte.output_columns}
563
+
564
+ new_output = []
565
+ for x in output_addresses:
566
+ new_output.append(mapping[x])
567
+ cte.output_columns = new_output
568
+ return cte
@@ -2,7 +2,11 @@ from typing import List, Union, Optional, Dict, Any, Sequence, Callable
2
2
 
3
3
  from jinja2 import Template
4
4
 
5
- from trilogy.core.processing.utility import is_scalar_condition, decompose_condition
5
+ from trilogy.core.processing.utility import (
6
+ is_scalar_condition,
7
+ decompose_condition,
8
+ sort_select_output,
9
+ )
6
10
  from trilogy.constants import CONFIG, logger, MagicConstants
7
11
  from trilogy.core.internal import DEFAULT_CONCEPTS
8
12
  from trilogy.core.enums import (
@@ -537,7 +541,7 @@ class BaseDialect:
537
541
  else:
538
542
  raise ValueError(f"Unable to render type {type(e)} {e}")
539
543
 
540
- def render_cte(self, cte: CTE):
544
+ def render_cte(self, cte: CTE, auto_sort: bool = True):
541
545
  if self.UNNEST_MODE in (
542
546
  UnnestMode.CROSS_APPLY,
543
547
  UnnestMode.CROSS_JOIN,
@@ -561,6 +565,8 @@ class BaseDialect:
561
565
  for c in cte.output_columns
562
566
  if c.address not in [y.address for y in cte.hidden_concepts]
563
567
  ]
568
+ if auto_sort:
569
+ select_columns = sorted(select_columns, key=lambda x: x)
564
570
  source: str | None = cte.base_name
565
571
  if not cte.render_from_clause:
566
572
  if len(cte.joins) > 0:
@@ -657,8 +663,11 @@ class BaseDialect:
657
663
  def generate_ctes(
658
664
  self,
659
665
  query: ProcessedQuery,
660
- ):
661
- return [self.render_cte(cte) for cte in query.ctes]
666
+ ) -> List[CompiledCTE]:
667
+ return [self.render_cte(cte) for cte in query.ctes[:-1]] + [
668
+ # last CTE needs to respect the user output order
669
+ self.render_cte(sort_select_output(query.ctes[-1], query), auto_sort=False)
670
+ ]
662
671
 
663
672
  def generate_queries(
664
673
  self,
@@ -790,11 +799,6 @@ class BaseDialect:
790
799
 
791
800
  compiled_ctes = self.generate_ctes(query)
792
801
 
793
- # restort selections by the order they were written in
794
- sorted_select: List[str] = []
795
- for output_c in output_addresses:
796
- sorted_select.append(select_columns[output_c])
797
-
798
802
  final = self.SQL_TEMPLATE.render(
799
803
  output=(
800
804
  query.output_to if isinstance(query, ProcessedQueryPersist) else None
File without changes
File without changes
File without changes
File without changes