pytrilogy 0.0.3.101__tar.gz → 0.0.3.103__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 (160) hide show
  1. {pytrilogy-0.0.3.101/pytrilogy.egg-info → pytrilogy-0.0.3.103}/PKG-INFO +2 -1
  2. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103/pytrilogy.egg-info}/PKG-INFO +2 -1
  3. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/pytrilogy.egg-info/SOURCES.txt +1 -0
  4. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/pytrilogy.egg-info/requires.txt +1 -0
  5. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/__init__.py +1 -1
  6. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/models/author.py +9 -0
  7. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/models/build.py +11 -4
  8. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/optimization.py +13 -4
  9. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/optimizations/__init__.py +2 -0
  10. pytrilogy-0.0.3.103/trilogy/core/optimizations/hide_unused_concept.py +51 -0
  11. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/discovery_node_factory.py +0 -5
  12. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/node_generators/rowset_node.py +0 -20
  13. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/node_generators/union_node.py +1 -1
  14. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/node_generators/unnest_node.py +24 -8
  15. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/nodes/base_node.py +0 -1
  16. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/dialect/base.py +17 -7
  17. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/dialect/common.py +5 -0
  18. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/parsing/common.py +11 -1
  19. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/parsing/parse_engine.py +3 -0
  20. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/parsing/render.py +125 -26
  21. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/parsing/trilogy.lark +3 -1
  22. pytrilogy-0.0.3.103/trilogy/std/display.preql +9 -0
  23. pytrilogy-0.0.3.101/trilogy/std/display.preql +0 -6
  24. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/LICENSE.md +0 -0
  25. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/README.md +0 -0
  26. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/pyproject.toml +0 -0
  27. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/pytrilogy.egg-info/dependency_links.txt +0 -0
  28. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/pytrilogy.egg-info/entry_points.txt +0 -0
  29. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/pytrilogy.egg-info/top_level.txt +0 -0
  30. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/setup.cfg +0 -0
  31. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/setup.py +0 -0
  32. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_datatypes.py +0 -0
  33. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_declarations.py +0 -0
  34. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_derived_concepts.py +0 -0
  35. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_discovery_nodes.py +0 -0
  36. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_enums.py +0 -0
  37. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_environment.py +0 -0
  38. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_execute_models.py +0 -0
  39. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_executor.py +0 -0
  40. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_failure.py +0 -0
  41. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_functions.py +0 -0
  42. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_imports.py +0 -0
  43. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_metadata.py +0 -0
  44. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_models.py +0 -0
  45. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_multi_join_assignments.py +0 -0
  46. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_parse_engine.py +0 -0
  47. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_parsing.py +0 -0
  48. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_parsing_failures.py +0 -0
  49. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_partial_handling.py +0 -0
  50. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_query_processing.py +0 -0
  51. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_query_render.py +0 -0
  52. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_select.py +0 -0
  53. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_show.py +0 -0
  54. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_statements.py +0 -0
  55. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_typing.py +0 -0
  56. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_undefined_concept.py +0 -0
  57. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_user_functions.py +0 -0
  58. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_validators.py +0 -0
  59. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/tests/test_where_clause.py +0 -0
  60. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/authoring/__init__.py +0 -0
  61. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/constants.py +0 -0
  62. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/__init__.py +0 -0
  63. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/constants.py +0 -0
  64. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/enums.py +0 -0
  65. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/env_processor.py +0 -0
  66. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/environment_helpers.py +0 -0
  67. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/ergonomics.py +0 -0
  68. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/exceptions.py +0 -0
  69. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/functions.py +0 -0
  70. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/graph_models.py +0 -0
  71. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/internal.py +0 -0
  72. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/models/__init__.py +0 -0
  73. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/models/build_environment.py +0 -0
  74. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/models/core.py +0 -0
  75. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/models/datasource.py +0 -0
  76. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/models/environment.py +0 -0
  77. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/models/execute.py +0 -0
  78. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/optimizations/base_optimization.py +0 -0
  79. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/optimizations/inline_datasource.py +0 -0
  80. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  81. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/__init__.py +0 -0
  82. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  83. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/discovery_utility.py +0 -0
  84. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/discovery_validation.py +0 -0
  85. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/graph_utils.py +0 -0
  86. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/node_generators/__init__.py +0 -0
  87. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  88. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/node_generators/common.py +0 -0
  89. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/node_generators/constant_node.py +0 -0
  90. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  91. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/node_generators/group_node.py +0 -0
  92. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  93. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  94. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  95. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/node_generators/recursive_node.py +0 -0
  96. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  97. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  98. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  99. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/node_generators/select_node.py +0 -0
  100. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  101. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/node_generators/window_node.py +0 -0
  102. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/nodes/__init__.py +0 -0
  103. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/nodes/filter_node.py +0 -0
  104. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/nodes/group_node.py +0 -0
  105. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/nodes/merge_node.py +0 -0
  106. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/nodes/recursive_node.py +0 -0
  107. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  108. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/nodes/union_node.py +0 -0
  109. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  110. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/nodes/window_node.py +0 -0
  111. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/processing/utility.py +0 -0
  112. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/query_processor.py +0 -0
  113. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/statements/__init__.py +0 -0
  114. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/statements/author.py +0 -0
  115. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/statements/build.py +0 -0
  116. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/statements/common.py +0 -0
  117. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/statements/execute.py +0 -0
  118. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/utility.py +0 -0
  119. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/validation/__init__.py +0 -0
  120. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/validation/common.py +0 -0
  121. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/validation/concept.py +0 -0
  122. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/validation/datasource.py +0 -0
  123. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/validation/environment.py +0 -0
  124. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/core/validation/fix.py +0 -0
  125. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/dialect/__init__.py +0 -0
  126. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/dialect/bigquery.py +0 -0
  127. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/dialect/config.py +0 -0
  128. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/dialect/dataframe.py +0 -0
  129. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/dialect/duckdb.py +0 -0
  130. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/dialect/enums.py +0 -0
  131. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/dialect/metadata.py +0 -0
  132. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/dialect/postgres.py +0 -0
  133. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/dialect/presto.py +0 -0
  134. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/dialect/snowflake.py +0 -0
  135. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/dialect/sql_server.py +0 -0
  136. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/engine.py +0 -0
  137. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/executor.py +0 -0
  138. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/hooks/__init__.py +0 -0
  139. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/hooks/base_hook.py +0 -0
  140. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/hooks/graph_hook.py +0 -0
  141. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/hooks/query_debugger.py +0 -0
  142. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/metadata/__init__.py +0 -0
  143. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/parser.py +0 -0
  144. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/parsing/__init__.py +0 -0
  145. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/parsing/config.py +0 -0
  146. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/parsing/exceptions.py +0 -0
  147. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/parsing/helpers.py +0 -0
  148. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/py.typed +0 -0
  149. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/render.py +0 -0
  150. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/scripts/__init__.py +0 -0
  151. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/scripts/trilogy.py +0 -0
  152. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/std/__init__.py +0 -0
  153. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/std/date.preql +0 -0
  154. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/std/geography.preql +0 -0
  155. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/std/metric.preql +0 -0
  156. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/std/money.preql +0 -0
  157. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/std/net.preql +0 -0
  158. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/std/ranking.preql +0 -0
  159. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/std/report.preql +0 -0
  160. {pytrilogy-0.0.3.101 → pytrilogy-0.0.3.103}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.101
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.101
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
@@ -68,6 +68,7 @@ trilogy/core/models/environment.py
68
68
  trilogy/core/models/execute.py
69
69
  trilogy/core/optimizations/__init__.py
70
70
  trilogy/core/optimizations/base_optimization.py
71
+ trilogy/core/optimizations/hide_unused_concept.py
71
72
  trilogy/core/optimizations/inline_datasource.py
72
73
  trilogy/core/optimizations/predicate_pushdown.py
73
74
  trilogy/core/processing/__init__.py
@@ -4,6 +4,7 @@ sqlalchemy<2.0.0
4
4
  networkx
5
5
  pyodbc
6
6
  pydantic
7
+ duckdb<1.4.0
7
8
  duckdb-engine
8
9
  click
9
10
 
@@ -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.101"
7
+ __version__ = "0.0.3.103"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -259,6 +259,15 @@ class Parenthetical(
259
259
  )
260
260
  )
261
261
 
262
+ def with_reference_replacement(self, source, target):
263
+ return Parenthetical.model_construct(
264
+ content=(
265
+ self.content.with_reference_replacement(source, target)
266
+ if isinstance(self.content, Mergeable)
267
+ else self.content
268
+ )
269
+ )
270
+
262
271
  @property
263
272
  def concept_arguments(self) -> Sequence[ConceptRef]:
264
273
  base: List[ConceptRef] = []
@@ -1511,7 +1511,10 @@ def requires_concept_nesting(
1511
1511
  ) -> AggregateWrapper | WindowItem | FilterItem | Function | None:
1512
1512
  if isinstance(expr, (AggregateWrapper, WindowItem, FilterItem)):
1513
1513
  return expr
1514
- if isinstance(expr, Function) and expr.operator == FunctionType.GROUP:
1514
+ if isinstance(expr, Function) and expr.operator in (
1515
+ FunctionType.GROUP,
1516
+ FunctionType.PARENTHETICAL,
1517
+ ):
1515
1518
  # group by requires nesting
1516
1519
  return expr
1517
1520
  return None
@@ -1696,13 +1699,12 @@ class Factory:
1696
1699
  return self._build_case_when(base)
1697
1700
 
1698
1701
  def _build_case_when(self, base: CaseWhen) -> BuildCaseWhen:
1699
- comparison = base.comparison
1700
1702
  expr: Concept | FuncArgs = base.expr
1701
1703
  validation = requires_concept_nesting(expr)
1702
1704
  if validation:
1703
1705
  expr, _ = self.instantiate_concept(validation)
1704
1706
  return BuildCaseWhen(
1705
- comparison=self.build(comparison),
1707
+ comparison=self.build(base.comparison),
1706
1708
  expr=self.build(expr),
1707
1709
  )
1708
1710
 
@@ -2019,7 +2021,12 @@ class Factory:
2019
2021
  return self._build_parenthetical(base)
2020
2022
 
2021
2023
  def _build_parenthetical(self, base: Parenthetical) -> BuildParenthetical:
2022
- return BuildParenthetical(content=(self.build(base.content)))
2024
+ validate = requires_concept_nesting(base.content)
2025
+ if validate:
2026
+ content, _ = self.instantiate_concept(validate)
2027
+ return BuildParenthetical(content=self.build(content))
2028
+ else:
2029
+ return BuildParenthetical(content=self.build(base.content))
2023
2030
 
2024
2031
  @build.register
2025
2032
  def _(self, base: SelectLineage) -> BuildSelectLineage:
@@ -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:
@@ -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
  ]
@@ -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 [])
@@ -249,11 +249,14 @@ def atom_is_relevant(
249
249
  return atom_is_relevant(atom.left, others, environment) or atom_is_relevant(
250
250
  atom.right, others, environment
251
251
  )
252
+ elif isinstance(atom, Parenthetical):
253
+ return atom_is_relevant(atom.content, others, environment)
252
254
  elif isinstance(atom, ConceptArgs):
253
255
  # use atom is relevant here to trigger the early exit behavior for concepts in set
254
256
  return any(
255
257
  [atom_is_relevant(x, others, environment) for x in atom.concept_arguments]
256
258
  )
259
+
257
260
  return False
258
261
 
259
262
 
@@ -294,12 +297,18 @@ def concept_is_relevant(
294
297
  if all([c in others for c in concept.grain.components]):
295
298
  return False
296
299
  if concept.derivation in (Derivation.BASIC,) and isinstance(
297
- concept.lineage, Function
300
+ concept.lineage, (Function, CaseWhen)
298
301
  ):
299
302
  relevant = False
300
303
  for arg in concept.lineage.arguments:
301
304
  relevant = atom_is_relevant(arg, others, environment) or relevant
305
+
302
306
  return relevant
307
+ if concept.derivation in (Derivation.BASIC,) and isinstance(
308
+ concept.lineage, Parenthetical
309
+ ):
310
+ return atom_is_relevant(concept.lineage.content, others, environment)
311
+
303
312
  if concept.granularity == Granularity.SINGLE_ROW:
304
313
  return False
305
314
  return True
@@ -346,6 +355,7 @@ def concepts_to_grain_concepts(
346
355
  if sub.address in seen:
347
356
  continue
348
357
  if not concept_is_relevant(sub, pconcepts, environment): # type: ignore
358
+
349
359
  continue
350
360
  seen.add(sub.address)
351
361
 
@@ -992,6 +992,9 @@ class ParseToObjects(Transformer):
992
992
  def order_by(self, args):
993
993
  return OrderBy(items=args[0])
994
994
 
995
+ def over_component(self, args):
996
+ return ConceptRef(address=args[0].value.lstrip(",").strip())
997
+
995
998
  def over_list(self, args):
996
999
  return [x for x in args]
997
1000