pytrilogy 0.0.3.113__tar.gz → 0.0.3.116__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 (173) hide show
  1. {pytrilogy-0.0.3.113/pytrilogy.egg-info → pytrilogy-0.0.3.116}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_functions.py +28 -1
  4. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_parse_engine.py +14 -0
  5. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/__init__.py +1 -1
  6. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/constants.py +29 -0
  7. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/enums.py +6 -1
  8. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/functions.py +33 -0
  9. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/models/author.py +126 -2
  10. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/models/build.py +70 -7
  11. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/models/environment.py +2 -1
  12. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/optimization.py +3 -2
  13. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/optimizations/hide_unused_concept.py +1 -5
  14. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/concept_strategies_v3.py +26 -5
  15. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/discovery_node_factory.py +2 -2
  16. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/discovery_utility.py +11 -4
  17. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/node_generators/basic_node.py +26 -15
  18. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/node_generators/common.py +4 -1
  19. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/node_generators/filter_node.py +7 -0
  20. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/node_generators/multiselect_node.py +3 -3
  21. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/node_generators/unnest_node.py +77 -6
  22. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/statements/author.py +4 -1
  23. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/dialect/base.py +42 -2
  24. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/executor.py +1 -1
  25. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/parsing/common.py +117 -20
  26. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/parsing/parse_engine.py +115 -5
  27. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/parsing/render.py +2 -1
  28. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/parsing/trilogy.lark +20 -7
  29. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/LICENSE.md +0 -0
  30. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/README.md +0 -0
  31. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/pyproject.toml +0 -0
  32. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/pytrilogy.egg-info/SOURCES.txt +0 -0
  33. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/pytrilogy.egg-info/dependency_links.txt +0 -0
  34. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/pytrilogy.egg-info/entry_points.txt +0 -0
  35. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/pytrilogy.egg-info/requires.txt +0 -0
  36. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/pytrilogy.egg-info/top_level.txt +0 -0
  37. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/requirements.txt +0 -0
  38. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/setup.cfg +0 -0
  39. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_datatypes.py +0 -0
  40. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_declarations.py +0 -0
  41. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_derived_concepts.py +0 -0
  42. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_discovery_nodes.py +0 -0
  43. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_enums.py +0 -0
  44. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_environment.py +0 -0
  45. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_execute_models.py +0 -0
  46. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_executor.py +0 -0
  47. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_failure.py +0 -0
  48. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_imports.py +0 -0
  49. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_metadata.py +0 -0
  50. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_models.py +0 -0
  51. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_multi_join_assignments.py +0 -0
  52. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_parsing.py +0 -0
  53. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_parsing_failures.py +0 -0
  54. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_partial_handling.py +0 -0
  55. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_query_processing.py +0 -0
  56. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_query_render.py +0 -0
  57. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_select.py +0 -0
  58. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_show.py +0 -0
  59. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_statements.py +0 -0
  60. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_typing.py +0 -0
  61. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_undefined_concept.py +0 -0
  62. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_user_functions.py +0 -0
  63. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_validators.py +0 -0
  64. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/tests/test_where_clause.py +0 -0
  65. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/ai/__init__.py +0 -0
  66. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/ai/constants.py +0 -0
  67. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/ai/conversation.py +0 -0
  68. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/ai/enums.py +0 -0
  69. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/ai/execute.py +0 -0
  70. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/ai/models.py +0 -0
  71. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/ai/prompts.py +0 -0
  72. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/ai/providers/__init__.py +0 -0
  73. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/ai/providers/anthropic.py +0 -0
  74. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/ai/providers/base.py +0 -0
  75. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/ai/providers/google.py +0 -0
  76. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/ai/providers/openai.py +0 -0
  77. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/ai/providers/utils.py +0 -0
  78. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/authoring/__init__.py +0 -0
  79. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/__init__.py +0 -0
  80. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/constants.py +0 -0
  81. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/env_processor.py +0 -0
  82. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/environment_helpers.py +0 -0
  83. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/ergonomics.py +0 -0
  84. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/exceptions.py +0 -0
  85. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/graph_models.py +0 -0
  86. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/internal.py +0 -0
  87. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/models/__init__.py +0 -0
  88. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/models/build_environment.py +0 -0
  89. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/models/core.py +0 -0
  90. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/models/datasource.py +0 -0
  91. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/models/execute.py +0 -0
  92. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/optimizations/__init__.py +0 -0
  93. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/optimizations/base_optimization.py +0 -0
  94. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/optimizations/inline_datasource.py +0 -0
  95. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  96. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/__init__.py +0 -0
  97. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/discovery_validation.py +0 -0
  98. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/graph_utils.py +0 -0
  99. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/node_generators/__init__.py +0 -0
  100. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/node_generators/constant_node.py +0 -0
  101. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/node_generators/group_node.py +0 -0
  102. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  103. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  104. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/node_generators/recursive_node.py +0 -0
  105. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  106. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  107. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  108. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  109. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/node_generators/select_node.py +0 -0
  110. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  111. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/node_generators/union_node.py +0 -0
  112. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/node_generators/window_node.py +0 -0
  113. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/nodes/__init__.py +0 -0
  114. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/nodes/base_node.py +0 -0
  115. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/nodes/filter_node.py +0 -0
  116. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/nodes/group_node.py +0 -0
  117. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/nodes/merge_node.py +0 -0
  118. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/nodes/recursive_node.py +0 -0
  119. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  120. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/nodes/union_node.py +0 -0
  121. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  122. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/nodes/window_node.py +0 -0
  123. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/processing/utility.py +0 -0
  124. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/query_processor.py +0 -0
  125. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/statements/__init__.py +0 -0
  126. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/statements/build.py +0 -0
  127. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/statements/common.py +0 -0
  128. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/statements/execute.py +0 -0
  129. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/utility.py +0 -0
  130. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/validation/__init__.py +0 -0
  131. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/validation/common.py +0 -0
  132. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/validation/concept.py +0 -0
  133. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/validation/datasource.py +0 -0
  134. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/validation/environment.py +0 -0
  135. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/core/validation/fix.py +0 -0
  136. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/dialect/__init__.py +0 -0
  137. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/dialect/bigquery.py +0 -0
  138. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/dialect/common.py +0 -0
  139. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/dialect/config.py +0 -0
  140. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/dialect/dataframe.py +0 -0
  141. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/dialect/duckdb.py +0 -0
  142. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/dialect/enums.py +0 -0
  143. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/dialect/metadata.py +0 -0
  144. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/dialect/postgres.py +0 -0
  145. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/dialect/presto.py +0 -0
  146. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/dialect/snowflake.py +0 -0
  147. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/dialect/sql_server.py +0 -0
  148. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/engine.py +0 -0
  149. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/hooks/__init__.py +0 -0
  150. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/hooks/base_hook.py +0 -0
  151. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/hooks/graph_hook.py +0 -0
  152. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/hooks/query_debugger.py +0 -0
  153. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/metadata/__init__.py +0 -0
  154. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/parser.py +0 -0
  155. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/parsing/__init__.py +0 -0
  156. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/parsing/config.py +0 -0
  157. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/parsing/exceptions.py +0 -0
  158. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/parsing/helpers.py +0 -0
  159. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/py.typed +0 -0
  160. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/render.py +0 -0
  161. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/scripts/__init__.py +0 -0
  162. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/scripts/trilogy.py +0 -0
  163. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/std/__init__.py +0 -0
  164. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/std/color.preql +0 -0
  165. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/std/date.preql +0 -0
  166. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/std/display.preql +0 -0
  167. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/std/geography.preql +0 -0
  168. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/std/metric.preql +0 -0
  169. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/std/money.preql +0 -0
  170. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/std/net.preql +0 -0
  171. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/std/ranking.preql +0 -0
  172. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/std/report.preql +0 -0
  173. {pytrilogy-0.0.3.113 → pytrilogy-0.0.3.116}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.113
3
+ Version: 0.0.3.116
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Classifier: Programming Language :: Python
6
6
  Classifier: Programming Language :: Python :: 3
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.113
3
+ Version: 0.0.3.116
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Classifier: Programming Language :: Python
6
6
  Classifier: Programming Language :: Python :: 3
@@ -168,7 +168,7 @@ def test_date_functions(test_environment):
168
168
 
169
169
  for dialect in TEST_DIALECTS:
170
170
  engine = None
171
- if isinstance(dialect, BigqueryDialect):
171
+ if isinstance(dialect, DuckDBDialect):
172
172
  engine = Dialects.DUCK_DB.default_executor(environment=test_environment)
173
173
  dialect.compile_statement(process_query(test_environment, select))
174
174
  if engine:
@@ -308,16 +308,43 @@ def test_string_functions(test_environment):
308
308
  property regex_replace <- regexp_replace(category_name, 'a', 'b');
309
309
 
310
310
  select
311
+ category_id,
311
312
  test_name,
312
313
  upper_name,
313
314
  lower_name,
314
315
  substring_name,
315
316
  strpos_name,
317
+ like_name,
318
+ like_alt,
319
+ regex_contains,
320
+ regex_substring,
321
+ regex_replace,
322
+ hash(category_name, md5) -> hash_md5,
323
+ hash(category_name, sha1) -> hash_sha1,
324
+ hash(category_name, sha256) -> hash_sha256,
325
+ # hash(category_name, sha512) -> hash_sha512
316
326
  ;"""
317
327
  env, parsed = parse(declarations, environment=test_environment)
318
328
  select: SelectStatement = parsed[-1]
319
329
  for dialect in TEST_DIALECTS:
330
+ engine = None
331
+ if isinstance(dialect, DuckDBDialect):
332
+ engine = Dialects.DUCK_DB.default_executor(environment=test_environment)
320
333
  dialect.compile_statement(process_query(test_environment, select))
334
+ if engine:
335
+ engine.execute_raw_sql(
336
+ """CREATE TABLE tblCategory AS
337
+ SELECT
338
+ 2 category_id,
339
+ 'category_a' as category_name
340
+ UNION ALL
341
+ SELECT
342
+ 3 category_id,
343
+ 'category_b' as category_name;"""
344
+ )
345
+ results = engine.execute_query(select)
346
+ assert results
347
+ assert results.fetchall()
321
348
 
322
349
 
323
350
  def test_case_function(test_environment):
@@ -121,3 +121,17 @@ def test_alias_error():
121
121
  with raises(InvalidSyntaxException) as e:
122
122
  env.parse(TEXT2)
123
123
  assert ERROR_CODES[201] in str(e.value), e.value
124
+
125
+
126
+ def test_semicolon_error():
127
+ env = Environment()
128
+ TEXT2 = """
129
+ const a <- 1;
130
+
131
+ select
132
+ a+2 as fun,
133
+
134
+ """
135
+ with raises(InvalidSyntaxException) as e:
136
+ env.parse(TEXT2)
137
+ assert ERROR_CODES[202] in str(e.value), e.value
@@ -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.113"
7
+ __version__ = "0.0.3.116"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -1,7 +1,9 @@
1
1
  import random
2
+ from contextlib import contextmanager
2
3
  from dataclasses import dataclass, field
3
4
  from enum import Enum
4
5
  from logging import getLogger
6
+ from typing import Any
5
7
 
6
8
  logger = getLogger("trilogy")
7
9
 
@@ -29,6 +31,7 @@ class Optimizations:
29
31
  constant_inlining: bool = True
30
32
  constant_inline_cutoff: int = 10
31
33
  direct_return: bool = True
34
+ hide_unused_concepts: bool = True
32
35
 
33
36
 
34
37
  @dataclass
@@ -50,6 +53,32 @@ class Rendering:
50
53
  parameters: bool = True
51
54
  concise: bool = False
52
55
 
56
+ @contextmanager
57
+ def temporary(self, **kwargs: Any):
58
+ """
59
+ Context manager to temporarily set attributes and revert them afterwards.
60
+
61
+ Usage:
62
+ r = Rendering()
63
+ with r.temporary(parameters=False, concise=True):
64
+ # parameters is False, concise is True here
65
+ do_something()
66
+ # parameters and concise are back to their original values
67
+ """
68
+ # Store original values
69
+ original_values = {key: getattr(self, key) for key in kwargs}
70
+
71
+ # Set new values
72
+ for key, value in kwargs.items():
73
+ setattr(self, key, value)
74
+
75
+ try:
76
+ yield self
77
+ finally:
78
+ # Restore original values
79
+ for key, value in original_values.items():
80
+ setattr(self, key, value)
81
+
53
82
 
54
83
  @dataclass
55
84
  class Parsing:
@@ -169,6 +169,7 @@ class FunctionType(Enum):
169
169
  ARRAY_SORT = "array_sort"
170
170
  ARRAY_TRANSFORM = "array_transform"
171
171
  ARRAY_TO_STRING = "array_to_string"
172
+ ARRAY_FILTER = "array_filter"
172
173
 
173
174
  # MAP
174
175
  MAP_KEYS = "map_keys"
@@ -204,6 +205,7 @@ class FunctionType(Enum):
204
205
  MIN = "min"
205
206
  AVG = "avg"
206
207
  ARRAY_AGG = "array_agg"
208
+ ANY = "any"
207
209
 
208
210
  # String
209
211
  LIKE = "like"
@@ -215,6 +217,7 @@ class FunctionType(Enum):
215
217
  CONTAINS = "contains"
216
218
  TRIM = "trim"
217
219
  REPLACE = "replace"
220
+ HASH = "hash"
218
221
 
219
222
  # STRING REGEX
220
223
  REGEXP_CONTAINS = "regexp_contains"
@@ -244,6 +247,7 @@ class FunctionType(Enum):
244
247
  DATE_ADD = "date_add"
245
248
  DATE_SUB = "date_sub"
246
249
  DATE_DIFF = "date_diff"
250
+ DATE_SPINE = "date_spine"
247
251
 
248
252
  # UNIX
249
253
  UNIX_TO_TIMESTAMP = "unix_to_timestamp"
@@ -263,6 +267,7 @@ class FunctionClass(Enum):
263
267
  FunctionType.ARRAY_AGG,
264
268
  FunctionType.COUNT,
265
269
  FunctionType.COUNT_DISTINCT,
270
+ FunctionType.ANY,
266
271
  ]
267
272
  SINGLE_ROW = [
268
273
  FunctionType.CONSTANT,
@@ -270,7 +275,7 @@ class FunctionClass(Enum):
270
275
  FunctionType.CURRENT_DATETIME,
271
276
  ]
272
277
 
273
- ONE_TO_MANY = [FunctionType.UNNEST]
278
+ ONE_TO_MANY = [FunctionType.UNNEST, FunctionType.DATE_SPINE]
274
279
 
275
280
  RECURSIVE = [FunctionType.RECURSE_EDGE]
276
281
 
@@ -212,6 +212,14 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
212
212
  output_type_function=get_unnest_output_type,
213
213
  arg_count=1,
214
214
  ),
215
+ FunctionType.DATE_SPINE: FunctionConfig(
216
+ valid_inputs={
217
+ DataType.DATE,
218
+ },
219
+ output_purpose=Purpose.KEY,
220
+ output_type=DataType.DATE,
221
+ arg_count=2,
222
+ ),
215
223
  FunctionType.RECURSE_EDGE: FunctionConfig(
216
224
  arg_count=2,
217
225
  ),
@@ -318,6 +326,18 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
318
326
  output_type_function=get_transform_output_type,
319
327
  arg_count=3,
320
328
  ),
329
+ FunctionType.ARRAY_FILTER: FunctionConfig(
330
+ valid_inputs=[
331
+ {
332
+ DataType.ARRAY,
333
+ },
334
+ {*DataType},
335
+ {*DataType},
336
+ ],
337
+ output_purpose=Purpose.PROPERTY,
338
+ output_type_function=get_transform_output_type,
339
+ arg_count=3,
340
+ ),
321
341
  FunctionType.ARRAY_TO_STRING: FunctionConfig(
322
342
  valid_inputs={
323
343
  DataType.ARRAY,
@@ -887,6 +907,11 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
887
907
  ),
888
908
  arg_count=1,
889
909
  ),
910
+ FunctionType.ANY: FunctionConfig(
911
+ valid_inputs={*DataType},
912
+ output_purpose=Purpose.PROPERTY,
913
+ arg_count=1,
914
+ ),
890
915
  FunctionType.AVG: FunctionConfig(
891
916
  valid_inputs={
892
917
  DataType.INTEGER,
@@ -903,6 +928,14 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
903
928
  output_type=DataType.TIMESTAMP,
904
929
  arg_count=1,
905
930
  ),
931
+ FunctionType.HASH: FunctionConfig(
932
+ valid_inputs={
933
+ DataType.STRING,
934
+ },
935
+ output_purpose=Purpose.PROPERTY,
936
+ output_type=DataType.STRING,
937
+ arg_count=2,
938
+ ),
906
939
  }
907
940
 
908
941
  EXCLUDED_FUNCTIONS = {
@@ -865,6 +865,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
865
865
  AggregateWrapper,
866
866
  RowsetItem,
867
867
  MultiSelectLineage,
868
+ Comparison,
868
869
  ]
869
870
  ] = None
870
871
  namespace: str = Field(default=DEFAULT_NAMESPACE, validate_default=True)
@@ -1079,6 +1080,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
1079
1080
  | AggregateWrapper
1080
1081
  | RowsetItem
1081
1082
  | MultiSelectLineage
1083
+ | Comparison
1082
1084
  | None,
1083
1085
  Grain,
1084
1086
  set[str] | None,
@@ -1178,6 +1180,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
1178
1180
  AggregateWrapper,
1179
1181
  RowsetItem,
1180
1182
  MultiSelectLineage,
1183
+ Comparison,
1181
1184
  ],
1182
1185
  output: List[ConceptRef],
1183
1186
  ):
@@ -1204,6 +1207,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
1204
1207
  def calculate_derivation(self, lineage, purpose: Purpose) -> Derivation:
1205
1208
  from trilogy.core.models.build import (
1206
1209
  BuildAggregateWrapper,
1210
+ BuildComparison,
1207
1211
  BuildFilterItem,
1208
1212
  BuildFunction,
1209
1213
  BuildMultiSelectLineage,
@@ -1221,6 +1225,8 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
1221
1225
  # return Derivation.PARENTHETICAL
1222
1226
  elif lineage and isinstance(lineage, (BuildRowsetItem, RowsetItem)):
1223
1227
  return Derivation.ROWSET
1228
+ elif lineage and isinstance(lineage, BuildComparison):
1229
+ return Derivation.BASIC
1224
1230
  elif lineage and isinstance(
1225
1231
  lineage, (BuildMultiSelectLineage, MultiSelectLineage)
1226
1232
  ):
@@ -1234,7 +1240,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
1234
1240
  elif (
1235
1241
  lineage
1236
1242
  and isinstance(lineage, (BuildFunction, Function))
1237
- and lineage.operator == FunctionType.UNNEST
1243
+ and lineage.operator in FunctionClass.ONE_TO_MANY.value
1238
1244
  ):
1239
1245
  return Derivation.UNNEST
1240
1246
  elif (
@@ -1286,7 +1292,8 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
1286
1292
  elif (
1287
1293
  lineage
1288
1294
  and isinstance(lineage, (Function, BuildFunction))
1289
- and lineage.operator in (FunctionType.UNNEST, FunctionType.UNION)
1295
+ and lineage.operator
1296
+ in (FunctionType.UNNEST, FunctionType.UNION, FunctionType.DATE_SPINE)
1290
1297
  ):
1291
1298
  return Granularity.MULTI_ROW
1292
1299
  elif lineage and all(
@@ -2132,6 +2139,89 @@ class AlignClause(Namespaced, BaseModel):
2132
2139
  )
2133
2140
 
2134
2141
 
2142
+ class DeriveItem(Namespaced, DataTyped, ConceptArgs, Mergeable, BaseModel):
2143
+ expr: Expr
2144
+ name: str
2145
+ namespace: str
2146
+
2147
+ @property
2148
+ def derived_concept(self) -> str:
2149
+ return f"{self.namespace}.{self.name}"
2150
+ # return ConceptRef(
2151
+ # address=f"{self.namespace}.{self.name}",
2152
+ # datatype=arg_to_datatype(self.expr),
2153
+ # )
2154
+
2155
+ def with_namespace(self, namespace):
2156
+ return DeriveItem.model_construct(
2157
+ expr=(self.expr.with_namespace(namespace) if self.expr else None),
2158
+ name=self.name,
2159
+ namespace=namespace,
2160
+ )
2161
+
2162
+ def with_merge(
2163
+ self, source: Concept, target: Concept, modifiers: List[Modifier]
2164
+ ) -> "DeriveItem":
2165
+ return DeriveItem.model_construct(
2166
+ expr=(
2167
+ self.expr.with_merge(source, target, modifiers)
2168
+ if isinstance(self.expr, Mergeable)
2169
+ else self.expr
2170
+ ),
2171
+ name=self.name,
2172
+ namespace=self.namespace,
2173
+ )
2174
+
2175
+ def with_reference_replacement(self, source, target):
2176
+ return DeriveItem.model_construct(
2177
+ expr=(
2178
+ self.expr.with_reference_replacement(source, target)
2179
+ if isinstance(self.expr, Mergeable)
2180
+ else self.expr
2181
+ ),
2182
+ name=self.name,
2183
+ namespace=self.namespace,
2184
+ )
2185
+
2186
+
2187
+ class DeriveClause(Mergeable, Namespaced, BaseModel):
2188
+ items: List[DeriveItem]
2189
+
2190
+ def with_namespace(self, namespace: str) -> "DeriveClause":
2191
+ return DeriveClause.model_construct(
2192
+ items=[
2193
+ x.with_namespace(namespace) if isinstance(x, Namespaced) else x
2194
+ for x in self.items
2195
+ ]
2196
+ )
2197
+
2198
+ def with_merge(
2199
+ self, source: Concept, target: Concept, modifiers: List[Modifier]
2200
+ ) -> "DeriveClause":
2201
+ return DeriveClause.model_construct(
2202
+ items=[
2203
+ (
2204
+ x.with_merge(source, target, modifiers)
2205
+ if isinstance(x, Mergeable)
2206
+ else x
2207
+ )
2208
+ for x in self.items
2209
+ ]
2210
+ )
2211
+
2212
+ def with_reference_replacement(self, source, target):
2213
+ return DeriveClause.model_construct(
2214
+ items=[
2215
+ (
2216
+ x.with_reference_replacement(source, target)
2217
+ if isinstance(x, Mergeable)
2218
+ else x
2219
+ )
2220
+ for x in self.items
2221
+ ]
2222
+ )
2223
+
2224
+
2135
2225
  class SelectLineage(Mergeable, Namespaced, BaseModel):
2136
2226
  selection: List[ConceptRef]
2137
2227
  hidden_components: set[str]
@@ -2176,15 +2266,40 @@ class SelectLineage(Mergeable, Namespaced, BaseModel):
2176
2266
  ),
2177
2267
  )
2178
2268
 
2269
+ def with_namespace(self, namespace):
2270
+ return SelectLineage.model_construct(
2271
+ selection=[x.with_namespace(namespace) for x in self.selection],
2272
+ hidden_components=self.hidden_components,
2273
+ local_concepts={
2274
+ x: y.with_namespace(namespace) for x, y in self.local_concepts.items()
2275
+ },
2276
+ order_by=self.order_by.with_namespace(namespace) if self.order_by else None,
2277
+ limit=self.limit,
2278
+ meta=self.meta,
2279
+ grain=self.grain.with_namespace(namespace),
2280
+ where_clause=(
2281
+ self.where_clause.with_namespace(namespace)
2282
+ if self.where_clause
2283
+ else None
2284
+ ),
2285
+ having_clause=(
2286
+ self.having_clause.with_namespace(namespace)
2287
+ if self.having_clause
2288
+ else None
2289
+ ),
2290
+ )
2291
+
2179
2292
 
2180
2293
  class MultiSelectLineage(Mergeable, ConceptArgs, Namespaced, BaseModel):
2181
2294
  selects: List[SelectLineage]
2182
2295
  align: AlignClause
2296
+
2183
2297
  namespace: str
2184
2298
  order_by: Optional[OrderBy] = None
2185
2299
  limit: Optional[int] = None
2186
2300
  where_clause: Union["WhereClause", None] = Field(default=None)
2187
2301
  having_clause: Union["HavingClause", None] = Field(default=None)
2302
+ derive: DeriveClause | None = None
2188
2303
  hidden_components: set[str]
2189
2304
 
2190
2305
  @property
@@ -2210,6 +2325,11 @@ class MultiSelectLineage(Mergeable, ConceptArgs, Namespaced, BaseModel):
2210
2325
  new = MultiSelectLineage.model_construct(
2211
2326
  selects=[s.with_merge(source, target, modifiers) for s in self.selects],
2212
2327
  align=self.align,
2328
+ derive=(
2329
+ self.derive.with_merge(source, target, modifiers)
2330
+ if self.derive
2331
+ else None
2332
+ ),
2213
2333
  namespace=self.namespace,
2214
2334
  hidden_components=self.hidden_components,
2215
2335
  order_by=(
@@ -2235,6 +2355,7 @@ class MultiSelectLineage(Mergeable, ConceptArgs, Namespaced, BaseModel):
2235
2355
  return MultiSelectLineage.model_construct(
2236
2356
  selects=[c.with_namespace(namespace) for c in self.selects],
2237
2357
  align=self.align.with_namespace(namespace),
2358
+ derive=self.derive.with_namespace(namespace) if self.derive else None,
2238
2359
  namespace=namespace,
2239
2360
  hidden_components=self.hidden_components,
2240
2361
  order_by=self.order_by.with_namespace(namespace) if self.order_by else None,
@@ -2256,6 +2377,9 @@ class MultiSelectLineage(Mergeable, ConceptArgs, Namespaced, BaseModel):
2256
2377
  output = set()
2257
2378
  for item in self.align.items:
2258
2379
  output.add(item.aligned_concept)
2380
+ if self.derive:
2381
+ for ditem in self.derive.items:
2382
+ output.add(ditem.derived_concept)
2259
2383
  return output
2260
2384
 
2261
2385
  @property
@@ -48,6 +48,8 @@ from trilogy.core.models.author import (
48
48
  Concept,
49
49
  ConceptRef,
50
50
  Conditional,
51
+ DeriveClause,
52
+ DeriveItem,
51
53
  FilterItem,
52
54
  FuncArgs,
53
55
  Function,
@@ -134,8 +136,9 @@ def concept_is_relevant(
134
136
  if concept.purpose in (Purpose.METRIC,):
135
137
  if all([c in others for c in concept.grain.components]):
136
138
  return False
139
+ if concept.derivation in (Derivation.UNNEST,):
140
+ return True
137
141
  if concept.derivation in (Derivation.BASIC,):
138
-
139
142
  return any(concept_is_relevant(c, others) for c in concept.concept_arguments)
140
143
  if concept.granularity == Granularity.SINGLE_ROW:
141
144
  return False
@@ -246,7 +249,7 @@ class BuildParamaterizedConceptReference(DataTyped):
246
249
  concept: BuildConcept
247
250
 
248
251
  def __str__(self):
249
- return f":{self.concept.address}"
252
+ return f":{self.concept.address.replace('.', '_')}"
250
253
 
251
254
  @property
252
255
  def safe_address(self) -> str:
@@ -1267,6 +1270,22 @@ class BuildAlignClause:
1267
1270
  items: List[BuildAlignItem]
1268
1271
 
1269
1272
 
1273
+ @dataclass
1274
+ class BuildDeriveClause:
1275
+ items: List[BuildDeriveItem]
1276
+
1277
+
1278
+ @dataclass
1279
+ class BuildDeriveItem:
1280
+ expr: BuildExpr
1281
+ name: str
1282
+ namespace: str = field(default=DEFAULT_NAMESPACE)
1283
+
1284
+ @property
1285
+ def address(self) -> str:
1286
+ return f"{self.namespace}.{self.name}"
1287
+
1288
+
1270
1289
  @dataclass
1271
1290
  class BuildSelectLineage:
1272
1291
  selection: List[BuildConcept]
@@ -1298,12 +1317,16 @@ class BuildMultiSelectLineage(BuildConceptArgs):
1298
1317
  limit: Optional[int] = None
1299
1318
  where_clause: Union["BuildWhereClause", None] = field(default=None)
1300
1319
  having_clause: Union["BuildHavingClause", None] = field(default=None)
1320
+ derive: BuildDeriveClause | None = None
1301
1321
 
1302
1322
  @property
1303
1323
  def derived_concepts(self) -> set[str]:
1304
1324
  output = set()
1305
1325
  for item in self.align.items:
1306
1326
  output.add(item.aligned_concept)
1327
+ if self.derive:
1328
+ for ditem in self.derive.items:
1329
+ output.add(ditem.address)
1307
1330
  return output
1308
1331
 
1309
1332
  @property
@@ -1311,10 +1334,12 @@ class BuildMultiSelectLineage(BuildConceptArgs):
1311
1334
  return self.build_output_components
1312
1335
 
1313
1336
  @property
1314
- def derived_concept(self) -> set[str]:
1315
- output = set()
1316
- for item in self.align.items:
1317
- output.add(item.aligned_concept)
1337
+ def calculated_derivations(self) -> set[str]:
1338
+ output: set[str] = set()
1339
+ if not self.derive:
1340
+ return output
1341
+ for item in self.derive.items:
1342
+ output.add(item.address)
1318
1343
  return output
1319
1344
 
1320
1345
  @property
@@ -1334,6 +1359,7 @@ class BuildMultiSelectLineage(BuildConceptArgs):
1334
1359
  for c in x.concepts:
1335
1360
  if c.address in cte.output_lcl:
1336
1361
  return c
1362
+
1337
1363
  raise SyntaxError(
1338
1364
  f"Could not find upstream map for multiselect {str(concept)} on cte ({cte})"
1339
1365
  )
@@ -1668,7 +1694,6 @@ class Factory:
1668
1694
  valid_inputs=base.valid_inputs,
1669
1695
  arg_count=base.arg_count,
1670
1696
  )
1671
-
1672
1697
  new = BuildFunction(
1673
1698
  operator=base.operator,
1674
1699
  arguments=[self.handle_constant(self.build(c)) for c in raw_args],
@@ -1724,6 +1749,14 @@ class Factory:
1724
1749
  return self._build_concept(base)
1725
1750
 
1726
1751
  def _build_concept(self, base: Concept) -> BuildConcept:
1752
+ try:
1753
+ return self.__build_concept(base)
1754
+ except RecursionError as e:
1755
+ raise RecursionError(
1756
+ f"Recursion error building concept {base.address}. This is likely due to a circular reference."
1757
+ ) from e
1758
+
1759
+ def __build_concept(self, base: Concept) -> BuildConcept:
1727
1760
  # TODO: if we are using parameters, wrap it in a new model and use that in rendering
1728
1761
  if base.address in self.local_concepts:
1729
1762
  return self.local_concepts[base.address]
@@ -1953,6 +1986,28 @@ class Factory:
1953
1986
  def _build_align_clause(self, base: AlignClause) -> BuildAlignClause:
1954
1987
  return BuildAlignClause(items=[self._build_align_item(x) for x in base.items])
1955
1988
 
1989
+ @build.register
1990
+ def _(self, base: DeriveItem) -> BuildDeriveItem:
1991
+ return self._build_derive_item(base)
1992
+
1993
+ def _build_derive_item(self, base: DeriveItem) -> BuildDeriveItem:
1994
+ expr: Concept | FuncArgs = base.expr
1995
+ validation = requires_concept_nesting(expr)
1996
+ if validation:
1997
+ expr, _ = self.instantiate_concept(validation)
1998
+ return BuildDeriveItem(
1999
+ expr=self.build(expr),
2000
+ name=base.name,
2001
+ namespace=base.namespace,
2002
+ )
2003
+
2004
+ @build.register
2005
+ def _(self, base: DeriveClause) -> BuildDeriveClause:
2006
+ return self._build_derive_clause(base)
2007
+
2008
+ def _build_derive_clause(self, base: DeriveClause) -> BuildDeriveClause:
2009
+ return BuildDeriveClause(items=[self.build(x) for x in base.items])
2010
+
1956
2011
  @build.register
1957
2012
  def _(self, base: RowsetItem) -> BuildRowsetItem:
1958
2013
  return self._build_rowset_item(base)
@@ -2002,6 +2057,13 @@ class Factory:
2002
2057
  def _build_tuple_wrapper(self, base: TupleWrapper) -> TupleWrapper:
2003
2058
  return TupleWrapper(val=[self.build(x) for x in base.val], type=base.type)
2004
2059
 
2060
+ @build.register
2061
+ def _(self, base: ListWrapper) -> ListWrapper:
2062
+ return self._build_list_wrapper(base)
2063
+
2064
+ def _build_list_wrapper(self, base: ListWrapper) -> ListWrapper:
2065
+ return ListWrapper([self.build(x) for x in base], type=base.type)
2066
+
2005
2067
  @build.register
2006
2068
  def _(self, base: FilterItem) -> BuildFilterItem:
2007
2069
  return self._build_filter_item(base)
@@ -2147,6 +2209,7 @@ class Factory:
2147
2209
  selects=base.selects,
2148
2210
  grain=final_grain,
2149
2211
  align=factory.build(base.align),
2212
+ derive=factory.build(base.derive) if base.derive else None,
2150
2213
  # self.align.with_select_context(
2151
2214
  # local_build_cache, self.grain, environment
2152
2215
  # ),
@@ -413,7 +413,8 @@ class Environment(BaseModel):
413
413
  self.imports[alias].append(imp_stm)
414
414
  # we can't exit early
415
415
  # as there may be new concepts
416
- for k, concept in source.concepts.items():
416
+ iteration: list[tuple[str, Concept]] = list(source.concepts.items())
417
+ for k, concept in iteration:
417
418
  # skip internal namespace
418
419
  if INTERNAL_NAMESPACE in concept.address:
419
420
  continue
@@ -228,7 +228,8 @@ def optimize_ctes(
228
228
  REGISTERED_RULES.append(PredicatePushdown())
229
229
  if CONFIG.optimizations.predicate_pushdown:
230
230
  REGISTERED_RULES.append(PredicatePushdownRemove())
231
- REGISTERED_RULES.append(HideUnusedConcepts())
231
+ if CONFIG.optimizations.hide_unused_concepts:
232
+ REGISTERED_RULES.append(HideUnusedConcepts())
232
233
  for rule in REGISTERED_RULES:
233
234
  loops = 0
234
235
  complete = False
@@ -242,7 +243,7 @@ def optimize_ctes(
242
243
  actions_taken = actions_taken or opt
243
244
  complete = not actions_taken
244
245
  loops += 1
245
- input = reorder_ctes(filter_irrelevant_ctes(input, root_cte))
246
+ input = reorder_ctes(filter_irrelevant_ctes(input, root_cte))
246
247
  logger.info(
247
248
  f"[Optimization] Finished checking for {type(rule).__name__} after {loops} loop(s)"
248
249
  )
@@ -39,11 +39,7 @@ class HideUnusedConcepts(OptimizationRule):
39
39
  self.log(
40
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
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
- ]
42
+ candidates = [x.address for x in cte.output_columns if x.address not in used]
47
43
  if len(candidates) == len(set([x.address for x in cte.output_columns])):
48
44
  # pop one out
49
45
  candidates.pop()