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

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.102
3
+ Version: 0.0.3.103
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -19,6 +19,7 @@ Requires-Dist: sqlalchemy<2.0.0
19
19
  Requires-Dist: networkx
20
20
  Requires-Dist: pyodbc
21
21
  Requires-Dist: pydantic
22
+ Requires-Dist: duckdb<1.4.0
22
23
  Requires-Dist: duckdb-engine
23
24
  Requires-Dist: click
24
25
  Provides-Extra: postgres
@@ -1,5 +1,5 @@
1
- pytrilogy-0.0.3.102.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
- trilogy/__init__.py,sha256=WZdbHlLqyuYo0xjcYkV5QDokunZLDlhGeibgoay48uc,304
1
+ pytrilogy-0.0.3.103.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
+ trilogy/__init__.py,sha256=lBanLP2CsDaPdJvJ3K68ncf8sO3sMfgsapECGG5J4fk,304
3
3
  trilogy/constants.py,sha256=ohmro6so7PPNp2ruWQKVc0ijjXYPOyRrxB9LI8dr3TU,1746
4
4
  trilogy/engine.py,sha256=3MiADf5MKcmxqiHBuRqiYdsXiLj7oitDfVvXvHrfjkA,2178
5
5
  trilogy/executor.py,sha256=KgCAQhHPT-j0rPkBbALX0f84W9-Q-bkjHayGuavg99w,16490
@@ -18,7 +18,7 @@ trilogy/core/exceptions.py,sha256=axkVXYJYQXCCwMHwlyDA232g4tCOwdCZUt7eHeUMDMg,28
18
18
  trilogy/core/functions.py,sha256=sdV6Z3NUVfwL1d18eNcaAXllVNqzLez23McsJ6xIp7M,33182
19
19
  trilogy/core/graph_models.py,sha256=4EWFTHGfYd72zvS2HYoV6hm7nMC_VEd7vWr6txY-ig0,3400
20
20
  trilogy/core/internal.py,sha256=r9QagDB2GvpqlyD_I7VrsfbVfIk5mnok2znEbv72Aa4,2681
21
- trilogy/core/optimization.py,sha256=ojpn-p79lr03SSVQbbw74iPCyoYpDYBmj1dbZ3oXCjI,8860
21
+ trilogy/core/optimization.py,sha256=Km0ITEx9n6Iv5ReX6tm4uXO5uniSv_ooahycNNiET3g,9212
22
22
  trilogy/core/query_processor.py,sha256=uqygDJqkjIH4vLP-lbGRgTN7rRcYEkr3KGqNimNw_80,20345
23
23
  trilogy/core/utility.py,sha256=3VC13uSQWcZNghgt7Ot0ZTeEmNqs__cx122abVq9qhM,410
24
24
  trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -29,13 +29,14 @@ trilogy/core/models/core.py,sha256=iT9WdZoiXeglmUHWn6bZyXCTBpkApTGPKtNm_Mhbu_g,1
29
29
  trilogy/core/models/datasource.py,sha256=wogTevZ-9CyUW2a8gjzqMCieircxi-J5lkI7EOAZnck,9596
30
30
  trilogy/core/models/environment.py,sha256=hwTIRnJgaHUdCYof7U5A9NPitGZ2s9yxqiW5O2SaJ9Y,28759
31
31
  trilogy/core/models/execute.py,sha256=lsNzNjS3nZvoW5CHjYwxDTwBe502NZyytpK1eq8CwW4,42357
32
- trilogy/core/optimizations/__init__.py,sha256=YH2-mGXZnVDnBcWVi8vTbrdw7Qs5TivG4h38rH3js_I,290
32
+ trilogy/core/optimizations/__init__.py,sha256=yspWc25M5SgAuvXYoSt5J8atyPbDlOfsKjIo5yGD9s4,368
33
33
  trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnbnb6vdVIztUfFQZsGnM,513
34
+ trilogy/core/optimizations/hide_unused_concept.py,sha256=DbsP8NqQOxmPv9omDOoFNPUGObUkqsRRNrr5d1xDxx4,1962
34
35
  trilogy/core/optimizations/inline_datasource.py,sha256=2sWNRpoRInnTgo9wExVT_r9RfLAQHI57reEV5cGHUcg,4329
35
36
  trilogy/core/optimizations/predicate_pushdown.py,sha256=g4AYE8Aw_iMlAh68TjNXGP754NTurrDduFECkUjoBnc,9399
36
37
  trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
38
  trilogy/core/processing/concept_strategies_v3.py,sha256=tvSN_aiqb1H7LkTl96vj7YK_DKcq_1nDdRJ69wZCLc8,22158
38
- trilogy/core/processing/discovery_node_factory.py,sha256=5QVYUsci_h6iYWhS0GCoDow2tSAipiBW1OyTRX-g_L8,15581
39
+ trilogy/core/processing/discovery_node_factory.py,sha256=r1JAnVhnB9YHEB1TW3racNH9mJvXjKRPZjzZrXsuiqg,15348
39
40
  trilogy/core/processing/discovery_utility.py,sha256=Xntgug6VnEF96uw5Zwen1qMEUwKjqrm_ZDUr4i4tc1U,5595
40
41
  trilogy/core/processing/discovery_validation.py,sha256=eZ4HfHMpqZLI8MGG2jez8arS8THs6ceuVrQFIY6gXrU,5364
41
42
  trilogy/core/processing/graph_utils.py,sha256=8QUVrkE9j-9C1AyrCb1nQEh8daCe0u1HuXl-Te85lag,1205
@@ -50,17 +51,17 @@ trilogy/core/processing/node_generators/group_to_node.py,sha256=jKcNCDOY6fNblrdZ
50
51
  trilogy/core/processing/node_generators/multiselect_node.py,sha256=GWV5yLmKTe1yyPhN60RG1Rnrn4ktfn9lYYXi_FVU4UI,7061
51
52
  trilogy/core/processing/node_generators/node_merge_node.py,sha256=1joMV7XpQ9Gpe-d5y7JUMBHIqakV5wFJi3Mtvs4UcL4,23415
52
53
  trilogy/core/processing/node_generators/recursive_node.py,sha256=l5zdh0dURKwmAy8kK4OpMtZfyUEQRk6N-PwSWIyBpSM,2468
53
- trilogy/core/processing/node_generators/rowset_node.py,sha256=5L5u6xz1In8EaHQdcYgR2si-tz9WB9YLXURo4AkUT9A,6630
54
+ trilogy/core/processing/node_generators/rowset_node.py,sha256=T11Rqj-tsfubjFvBO0rzIVxtv9tOwwKXjGyut0r9xIY,5919
54
55
  trilogy/core/processing/node_generators/select_merge_node.py,sha256=KQvGoNT5ZBWQ_caEomRTtG1PKZC7OPT4PKfY0QmwMGE,22270
55
56
  trilogy/core/processing/node_generators/select_node.py,sha256=Ta1G39V94gjX_AgyZDz9OqnwLz4BjY3D6Drx9YpziMQ,3555
56
57
  trilogy/core/processing/node_generators/synonym_node.py,sha256=AnAsa_Wj50NJ_IK0HSgab_7klYmKVrv0WI1uUe-GvEY,3766
57
- trilogy/core/processing/node_generators/union_node.py,sha256=VNo6Oey4p8etU9xrOh2oTT2lIOTvY6PULUPRvVa2uxU,2877
58
- trilogy/core/processing/node_generators/unnest_node.py,sha256=w9vhPzASz53QPASLqFcLDdR9eY132tgVUcp3QolD5Jw,3726
58
+ trilogy/core/processing/node_generators/union_node.py,sha256=NxQbnRRoYMI4WjMeph41yk4E6yipj53qdGuNt-Mozxw,2818
59
+ trilogy/core/processing/node_generators/unnest_node.py,sha256=7uOZzBidEEKeZE0VW_XlgHGhEYf_snEHtV8UgJ_ZjyY,4048
59
60
  trilogy/core/processing/node_generators/window_node.py,sha256=A90linr4pkZtTNfn9k2YNLqrJ_SFII3lbHxB-BC6mI8,6688
60
61
  trilogy/core/processing/node_generators/select_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
62
  trilogy/core/processing/node_generators/select_helpers/datasource_injection.py,sha256=m2YQ4OmG0N2O61a7NEq1ZzbTa7JsCC00lxB2ymjcYRI,8224
62
63
  trilogy/core/processing/nodes/__init__.py,sha256=zTge1EzwzEydlcMliIFO_TT7h7lS8l37lyZuQDir1h0,5487
63
- trilogy/core/processing/nodes/base_node.py,sha256=C_CjlOzlGMXckyV0b_PJZerpopNesRCKfambMq7Asvc,18221
64
+ trilogy/core/processing/nodes/base_node.py,sha256=TQZLEz_xfXpdVyFa9R5BwvikH1OqzJUioOPw8vTETWc,18144
64
65
  trilogy/core/processing/nodes/filter_node.py,sha256=5VtRfKbCORx0dV-vQfgy3gOEkmmscL9f31ExvlODwvY,2461
65
66
  trilogy/core/processing/nodes/group_node.py,sha256=njz-5T7OJ3-kaBC7EhdtPra3G77HnI7apjUwMGhUeXo,10569
66
67
  trilogy/core/processing/nodes/merge_node.py,sha256=daJywBxh44Gqk-7eTiXbYtY7xo6O6fNvqX-DagTOTmE,16231
@@ -81,9 +82,9 @@ trilogy/core/validation/datasource.py,sha256=nJeEFyb6iMBwlEVdYVy1vLzAbdRZwOsUjGx
81
82
  trilogy/core/validation/environment.py,sha256=ymvhQyt7jLK641JAAIQkqjQaAmr9C5022ILzYvDgPP0,2835
82
83
  trilogy/core/validation/fix.py,sha256=Z818UFNLxndMTLiyhB3doLxIfnOZ-16QGvVFWuD7UsA,3750
83
84
  trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
84
- trilogy/dialect/base.py,sha256=d2gXfa5Jh3uyN9H9MxG53JT-xQQgntq2X7EprobJYUc,49698
85
+ trilogy/dialect/base.py,sha256=mgARj-aldkFAqdwps_25da03NLIAxU6Xg9Jq_VcOtp0,50181
85
86
  trilogy/dialect/bigquery.py,sha256=XS3hpybeowgfrOrkycAigAF3NX2YUzTzfgE6f__2fT4,4316
86
- trilogy/dialect/common.py,sha256=_MarnMWRBn3VcNt3k5VUdFrwH6oHzGdNQquSpHNLq4o,5644
87
+ trilogy/dialect/common.py,sha256=cUI7JMmpG_A5KcaxRI-GoyqwLMD6jTf0JJhgcOdwQK4,5833
87
88
  trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
88
89
  trilogy/dialect/dataframe.py,sha256=RUbNgReEa9g3pL6H7fP9lPTrAij5pkqedpZ99D8_5AE,1522
89
90
  trilogy/dialect/duckdb.py,sha256=JoUvQ19WvgxoaJkGLM7DPXOd1H0394k3vBiblksQzOI,5631
@@ -117,8 +118,8 @@ trilogy/std/money.preql,sha256=XWwvAV3WxBsHX9zfptoYRnBigcfYwrYtBHXTME0xJuQ,2082
117
118
  trilogy/std/net.preql,sha256=WZCuvH87_rZntZiuGJMmBDMVKkdhTtxeHOkrXNwJ1EE,416
118
119
  trilogy/std/ranking.preql,sha256=LDoZrYyz4g3xsII9XwXfmstZD-_92i1Eox1UqkBIfi8,83
119
120
  trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
120
- pytrilogy-0.0.3.102.dist-info/METADATA,sha256=fQKKWHDkY9Nhofow6RO22oMSXp91H-vOD5d3kk3S-V8,11811
121
- pytrilogy-0.0.3.102.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
122
- pytrilogy-0.0.3.102.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
123
- pytrilogy-0.0.3.102.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
124
- pytrilogy-0.0.3.102.dist-info/RECORD,,
121
+ pytrilogy-0.0.3.103.dist-info/METADATA,sha256=RnMfz8EH2sCtqHEDAraYhAb_V7oPbovtuI3PsL2F3Ms,11839
122
+ pytrilogy-0.0.3.103.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
123
+ pytrilogy-0.0.3.103.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
124
+ pytrilogy-0.0.3.103.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
125
+ pytrilogy-0.0.3.103.dist-info/RECORD,,
trilogy/__init__.py CHANGED
@@ -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.3.102"
7
+ __version__ = "0.0.3.103"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -5,6 +5,7 @@ from trilogy.core.models.build import (
5
5
  )
6
6
  from trilogy.core.models.execute import CTE, RecursiveCTE, UnionCTE
7
7
  from trilogy.core.optimizations import (
8
+ HideUnusedConcepts,
8
9
  InlineDatasource,
9
10
  OptimizationRule,
10
11
  PredicatePushdown,
@@ -84,11 +85,18 @@ def filter_irrelevant_ctes(
84
85
  # child.existence_source_map[x2].append(parent.name)
85
86
  # else:
86
87
  relevant_ctes.add(cte.name)
87
- for cte in cte.parent_ctes:
88
- recurse(cte, inverse_map)
88
+
89
+ for parent in cte.parent_ctes:
90
+ if parent.name in relevant_ctes:
91
+ logger.info(
92
+ f"[Optimization][Irrelevent CTE filtering] Already visited {parent.name} when visting {cte.name}, potential recursive dag"
93
+ )
94
+ continue
95
+
96
+ recurse(parent, inverse_map)
89
97
  if isinstance(cte, UnionCTE):
90
- for cte in cte.internal_ctes:
91
- recurse(cte, inverse_map)
98
+ for internal in cte.internal_ctes:
99
+ recurse(internal, inverse_map)
92
100
 
93
101
  inverse_map = gen_inverse_map(input)
94
102
  recurse(root_cte, inverse_map)
@@ -220,6 +228,7 @@ def optimize_ctes(
220
228
  REGISTERED_RULES.append(PredicatePushdown())
221
229
  if CONFIG.optimizations.predicate_pushdown:
222
230
  REGISTERED_RULES.append(PredicatePushdownRemove())
231
+ REGISTERED_RULES.append(HideUnusedConcepts())
223
232
  for rule in REGISTERED_RULES:
224
233
  loops = 0
225
234
  complete = False
@@ -1,4 +1,5 @@
1
1
  from .base_optimization import OptimizationRule
2
+ from .hide_unused_concept import HideUnusedConcepts
2
3
  from .inline_datasource import InlineDatasource
3
4
  from .predicate_pushdown import PredicatePushdown, PredicatePushdownRemove
4
5
 
@@ -7,4 +8,5 @@ __all__ = [
7
8
  "InlineDatasource",
8
9
  "PredicatePushdown",
9
10
  "PredicatePushdownRemove",
11
+ "HideUnusedConcepts",
10
12
  ]
@@ -0,0 +1,51 @@
1
+ from trilogy.core.models.build import (
2
+ BuildConcept,
3
+ )
4
+ from trilogy.core.models.execute import CTE, UnionCTE
5
+ from trilogy.core.optimizations.base_optimization import OptimizationRule
6
+
7
+
8
+ class HideUnusedConcepts(OptimizationRule):
9
+ def __init__(self, *args, **kwargs) -> None:
10
+ super().__init__(*args, **kwargs)
11
+
12
+ def optimize(
13
+ self, cte: CTE | UnionCTE, inverse_map: dict[str, list[CTE | UnionCTE]]
14
+ ) -> bool:
15
+ used = set()
16
+ from trilogy.dialect.base import BaseDialect
17
+
18
+ renderer = BaseDialect()
19
+ children = inverse_map.get(cte.name, [])
20
+ if not children:
21
+ return False
22
+ for v in children:
23
+ self.log(f"Analyzing usage of {cte.name} in {v.name}")
24
+ renderer.render_cte(v)
25
+ used = renderer.used_map.get(cte.name, set())
26
+ self.log(f"Used concepts for {cte.name}: {used} from {renderer.used_map}")
27
+ add_to_hidden: list[BuildConcept] = []
28
+ for concept in cte.output_columns:
29
+ if concept.address not in used:
30
+ add_to_hidden.append(concept)
31
+ newly_hidden = [
32
+ x.address for x in add_to_hidden if x.address not in cte.hidden_concepts
33
+ ]
34
+ non_hidden = [
35
+ x for x in cte.output_columns if x.address not in cte.hidden_concepts
36
+ ]
37
+ if not newly_hidden or len(non_hidden) <= 1:
38
+ return False
39
+ self.log(
40
+ f"Hiding unused concepts {[x.address for x in add_to_hidden]} from {cte.name} (used: {used}, all: {[x.address for x in cte.output_columns]})"
41
+ )
42
+ candidates = [
43
+ x.address
44
+ for x in cte.output_columns
45
+ if x.address not in used and x.address not in cte.hidden_concepts
46
+ ]
47
+ if len(candidates) == len(set([x.address for x in cte.output_columns])):
48
+ # pop one out
49
+ candidates.pop()
50
+ cte.hidden_concepts = set(candidates)
51
+ return True
@@ -376,11 +376,6 @@ class RootNodeHandler:
376
376
 
377
377
  if pseudonyms:
378
378
  expanded.add_output_concepts(pseudonyms)
379
- logger.info(
380
- f"{depth_to_prefix(self.ctx.depth)}{LOGGER_PREFIX} "
381
- f"Hiding pseudonyms {[c.address for c in pseudonyms]}"
382
- )
383
- expanded.hide_output_concepts(pseudonyms)
384
379
 
385
380
  logger.info(
386
381
  f"{depth_to_prefix(self.ctx.depth)}{LOGGER_PREFIX} "
@@ -64,14 +64,6 @@ def gen_rowset_node(
64
64
  v for v in concept_pool if v.address in rowset_outputs
65
65
  ]
66
66
 
67
- select_hidden = node.hidden_concepts
68
- rowset_hidden = [
69
- x
70
- for x in rowset_relevant
71
- if x.address in lineage.rowset.derived_concepts
72
- and isinstance(x.lineage, BuildRowsetItem)
73
- and x.lineage.content.address in select_hidden
74
- ]
75
67
  additional_relevant = [
76
68
  factory.build(x) for x in select.output_components if x.address in enrichment
77
69
  ]
@@ -84,18 +76,6 @@ def gen_rowset_node(
84
76
  )
85
77
  node.partial_concepts.append(item)
86
78
 
87
- final_hidden = rowset_hidden + [
88
- x
89
- for x in node.output_concepts
90
- if x.address not in local_optional + [concept]
91
- and x.derivation != Derivation.ROWSET
92
- and not any(z in lineage.rowset.derived_concepts for z in x.pseudonyms)
93
- ]
94
- logger.info(
95
- f"{padding(depth)}{LOGGER_PREFIX} hiding {final_hidden} local optional {local_optional}"
96
- )
97
- node.hide_output_concepts(final_hidden)
98
-
99
79
  node.grain = BuildGrain.from_concepts(
100
80
  [
101
81
  x
@@ -74,7 +74,7 @@ def gen_union_node(
74
74
  history=history,
75
75
  conditions=conditions,
76
76
  )
77
- parent.hide_output_concepts(parent.output_concepts)
77
+
78
78
  parent.add_output_concepts(resolved)
79
79
  parent_nodes.append(parent)
80
80
  if not parent:
@@ -1,9 +1,19 @@
1
1
  from typing import List
2
2
 
3
3
  from trilogy.constants import logger
4
- from trilogy.core.models.build import BuildConcept, BuildFunction, BuildWhereClause
4
+ from trilogy.core.models.build import (
5
+ BuildConcept,
6
+ BuildFunction,
7
+ BuildGrain,
8
+ BuildWhereClause,
9
+ )
5
10
  from trilogy.core.models.build_environment import BuildEnvironment
6
- from trilogy.core.processing.nodes import History, StrategyNode, UnnestNode
11
+ from trilogy.core.processing.nodes import (
12
+ History,
13
+ StrategyNode,
14
+ UnnestNode,
15
+ WhereSafetyNode,
16
+ )
7
17
  from trilogy.core.processing.utility import padding
8
18
 
9
19
  LOGGER_PREFIX = "[GEN_UNNEST_NODE]"
@@ -71,7 +81,9 @@ def gen_unnest_node(
71
81
  return None
72
82
  else:
73
83
  parent = None
74
-
84
+ logger.info(
85
+ f"{depth_prefix}{LOGGER_PREFIX} unnest node for {concept} got parent {parent}"
86
+ )
75
87
  base = UnnestNode(
76
88
  unnest_concepts=[concept] + equivalent_optional,
77
89
  input_concepts=arguments + non_equivalent_optional,
@@ -83,7 +95,7 @@ def gen_unnest_node(
83
95
  # as unnest operations are not valid in all situations
84
96
  # TODO: inline this node when we can detect it's safe
85
97
  conditional = conditions.conditional if conditions else None
86
- new = StrategyNode(
98
+ new = WhereSafetyNode(
87
99
  input_concepts=base.output_concepts,
88
100
  output_concepts=base.output_concepts,
89
101
  environment=environment,
@@ -92,9 +104,13 @@ def gen_unnest_node(
92
104
  preexisting_conditions=(
93
105
  conditional if conditional and local_conditions is False else None
94
106
  ),
107
+ grain=BuildGrain.from_concepts(
108
+ concepts=base.output_concepts,
109
+ environment=environment,
110
+ ),
95
111
  )
96
- qds = new.resolve()
97
- assert qds.source_map[concept.address] == {base.resolve()}
98
- for x in equivalent_optional:
99
- assert qds.source_map[x.address] == {base.resolve()}
112
+ # qds = new.resolve()
113
+ # assert qds.source_map[concept.address] == {base.resolve()}
114
+ # for x in equivalent_optional:
115
+ # assert qds.source_map[x.address] == {base.resolve()}
100
116
  return new
@@ -489,7 +489,6 @@ class WhereSafetyNode(StrategyNode):
489
489
  parent = parent.copy()
490
490
  # avoid performance hit by not rebuilding until end
491
491
  parent.set_output_concepts(self.output_concepts, rebuild=False)
492
- parent.hide_output_concepts(self.hidden_concepts, rebuild=False)
493
492
 
494
493
  # these conditions
495
494
  if self.preexisting_conditions:
trilogy/dialect/base.py CHANGED
@@ -1,3 +1,4 @@
1
+ from collections import defaultdict
1
2
  from datetime import date, datetime
2
3
  from typing import Any, Callable, Dict, List, Optional, Sequence, Union
3
4
 
@@ -311,6 +312,7 @@ def safe_get_cte_value(
311
312
  c: BuildConcept,
312
313
  quote_char: str,
313
314
  render_expr: Callable,
315
+ use_map: dict[str, set[str]],
314
316
  ) -> Optional[str]:
315
317
  address = c.address
316
318
  raw = cte.source_map.get(address, None)
@@ -319,13 +321,17 @@ def safe_get_cte_value(
319
321
  return None
320
322
  if isinstance(raw, str):
321
323
  rendered = cte.get_alias(c, raw)
324
+ use_map[raw].add(c.address)
322
325
  return f"{quote_char}{raw}{quote_char}.{safe_quote(rendered, quote_char)}"
323
326
  if isinstance(raw, list) and len(raw) == 1:
324
327
  rendered = cte.get_alias(c, raw[0])
325
328
  if isinstance(rendered, FUNCTION_ITEMS):
326
329
  # if it's a function, we need to render it as a function
327
330
  return f"{render_expr(rendered, cte=cte, raise_invalid=True)}"
331
+ use_map[raw[0]].add(c.address)
328
332
  return f"{quote_char}{raw[0]}{quote_char}.{safe_quote(rendered, quote_char)}"
333
+ for x in raw:
334
+ use_map[x].add(c.address)
329
335
  return coalesce(
330
336
  sorted(
331
337
  [
@@ -350,13 +356,12 @@ class BaseDialect:
350
356
 
351
357
  def __init__(self, rendering: Rendering | None = None):
352
358
  self.rendering = rendering or CONFIG.rendering
359
+ self.used_map: dict[str, set[str]] = defaultdict(set)
353
360
 
354
361
  def render_order_item(
355
362
  self,
356
363
  order_item: BuildOrderItem,
357
364
  cte: CTE | UnionCTE,
358
- final: bool = False,
359
- alias: bool = True,
360
365
  ) -> str:
361
366
  # if final:
362
367
  # if not alias:
@@ -527,6 +532,9 @@ class BaseDialect:
527
532
  )
528
533
 
529
534
  raw_content = cte.get_alias(c)
535
+ parent = cte.source_map.get(c.address, None)
536
+ if parent:
537
+ self.used_map[parent[0]].add(c.address)
530
538
  if isinstance(raw_content, RawColumnExpr):
531
539
  rval = raw_content.text
532
540
  elif isinstance(raw_content, FUNCTION_ITEMS):
@@ -540,6 +548,7 @@ class BaseDialect:
540
548
  c,
541
549
  self.QUOTE_CHARACTER,
542
550
  self.render_expr,
551
+ self.used_map,
543
552
  )
544
553
  if not rval:
545
554
  # unions won't have a specific source mapped; just use a generic column reference
@@ -615,6 +624,7 @@ class BaseDialect:
615
624
  lookup_cte = cte
616
625
  if cte_map and not lookup_cte:
617
626
  lookup_cte = cte_map.get(e.right.address)
627
+
618
628
  assert lookup_cte, "Subselects must be rendered with a CTE in context"
619
629
  if e.right.address not in lookup_cte.existence_source_map:
620
630
  lookup = lookup_cte.source_map.get(
@@ -634,6 +644,7 @@ class BaseDialect:
634
644
  f"Missing source CTE for {e.right.address}"
635
645
  )
636
646
  assert cte, "CTE must be provided for inlined CTEs"
647
+ self.used_map[target].add(e.right.address)
637
648
  if target in cte.inlined_ctes:
638
649
  info = cte.inlined_ctes[target]
639
650
  return f"{self.render_expr(e.left, cte=cte, cte_map=cte_map, raise_invalid=raise_invalid)} {e.operator.value} (select {target}.{self.QUOTE_CHARACTER}{e.right.safe_address}{self.QUOTE_CHARACTER} from {info.new_base} as {target} where {target}.{self.QUOTE_CHARACTER}{e.right.safe_address}{self.QUOTE_CHARACTER} is not null)"
@@ -738,6 +749,7 @@ class BaseDialect:
738
749
  raise_invalid=raise_invalid,
739
750
  )
740
751
  elif cte_map:
752
+ self.used_map[cte_map[e.address].name].add(e.address)
741
753
  return f"{cte_map[e.address].name}.{self.QUOTE_CHARACTER}{e.safe_address}{self.QUOTE_CHARACTER}"
742
754
  return f"{self.QUOTE_CHARACTER}{e.safe_address}{self.QUOTE_CHARACTER}"
743
755
  elif isinstance(e, bool):
@@ -822,10 +834,7 @@ class BaseDialect:
822
834
  )
823
835
  if cte.order_by:
824
836
 
825
- ordering = [
826
- self.render_order_item(i, cte, final=True, alias=False)
827
- for i in cte.order_by.items
828
- ]
837
+ ordering = [self.render_order_item(i, cte) for i in cte.order_by.items]
829
838
  base_statement += "\nORDER BY " + ",".join(ordering)
830
839
  return CompiledCTE(name=cte.name, statement=base_statement)
831
840
  elif isinstance(cte, RecursiveCTE):
@@ -950,7 +959,8 @@ class BaseDialect:
950
959
  self.QUOTE_CHARACTER,
951
960
  self.render_expr,
952
961
  cte,
953
- self.UNNEST_MODE,
962
+ use_map=self.used_map,
963
+ unnest_mode=self.UNNEST_MODE,
954
964
  )
955
965
  for join in final_joins
956
966
  ]
trilogy/dialect/common.py CHANGED
@@ -62,10 +62,12 @@ def render_join_concept(
62
62
  concept: BuildConcept,
63
63
  render_expr,
64
64
  inlined_ctes: set[str],
65
+ use_map: dict[str, set[str]],
65
66
  ):
66
67
  if cte.name in inlined_ctes:
67
68
  base = render_expr(concept, cte)
68
69
  return base
70
+ use_map[name].add(concept.address)
69
71
  return f"{quote_character}{name}{quote_character}.{quote_character}{concept.safe_address}{quote_character}"
70
72
 
71
73
 
@@ -85,6 +87,7 @@ def render_join(
85
87
  str,
86
88
  ],
87
89
  cte: CTE,
90
+ use_map: dict[str, set[str]],
88
91
  unnest_mode: UnnestMode = UnnestMode.CROSS_APPLY,
89
92
  ) -> str | None:
90
93
  # {% for key in join.joinkeys %}{{ key.inner }} = {{ key.outer}}{% endfor %}
@@ -121,6 +124,7 @@ def render_join(
121
124
  pair.left,
122
125
  render_expr_func,
123
126
  join.inlined_ctes,
127
+ use_map=use_map,
124
128
  ),
125
129
  render_join_concept(
126
130
  right_name,
@@ -129,6 +133,7 @@ def render_join(
129
133
  pair.right,
130
134
  render_expr_func,
131
135
  join.inlined_ctes,
136
+ use_map=use_map,
132
137
  ),
133
138
  modifiers=pair.modifiers
134
139
  + (pair.left.modifiers or [])