pytrilogy 0.0.3.115__tar.gz → 0.0.3.117__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.115/pytrilogy.egg-info → pytrilogy-0.0.3.117}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_functions.py +28 -1
  4. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/__init__.py +1 -1
  5. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/constants.py +1 -0
  6. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/enums.py +1 -0
  7. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/functions.py +8 -0
  8. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/models/author.py +123 -0
  9. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/models/build.py +53 -5
  10. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/models/environment.py +2 -1
  11. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/optimization.py +3 -2
  12. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/optimizations/hide_unused_concept.py +1 -5
  13. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/concept_strategies_v3.py +2 -0
  14. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/node_generators/basic_node.py +26 -15
  15. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/node_generators/common.py +4 -1
  16. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/node_generators/multiselect_node.py +3 -3
  17. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/statements/author.py +3 -0
  18. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/dialect/base.py +30 -2
  19. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/executor.py +1 -1
  20. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/parsing/common.py +109 -17
  21. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/parsing/parse_engine.py +80 -11
  22. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/parsing/trilogy.lark +11 -3
  23. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/LICENSE.md +0 -0
  24. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/README.md +0 -0
  25. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/pyproject.toml +0 -0
  26. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/pytrilogy.egg-info/SOURCES.txt +0 -0
  27. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/pytrilogy.egg-info/dependency_links.txt +0 -0
  28. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/pytrilogy.egg-info/entry_points.txt +0 -0
  29. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/pytrilogy.egg-info/requires.txt +0 -0
  30. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/pytrilogy.egg-info/top_level.txt +0 -0
  31. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/requirements.txt +0 -0
  32. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/setup.cfg +0 -0
  33. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_datatypes.py +0 -0
  34. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_declarations.py +0 -0
  35. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_derived_concepts.py +0 -0
  36. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_discovery_nodes.py +0 -0
  37. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_enums.py +0 -0
  38. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_environment.py +0 -0
  39. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_execute_models.py +0 -0
  40. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_executor.py +0 -0
  41. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_failure.py +0 -0
  42. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_imports.py +0 -0
  43. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_metadata.py +0 -0
  44. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_models.py +0 -0
  45. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_multi_join_assignments.py +0 -0
  46. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_parse_engine.py +0 -0
  47. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_parsing.py +0 -0
  48. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_parsing_failures.py +0 -0
  49. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_partial_handling.py +0 -0
  50. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_query_processing.py +0 -0
  51. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_query_render.py +0 -0
  52. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_select.py +0 -0
  53. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_show.py +0 -0
  54. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_statements.py +0 -0
  55. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_typing.py +0 -0
  56. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_undefined_concept.py +0 -0
  57. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_user_functions.py +0 -0
  58. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_validators.py +0 -0
  59. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/tests/test_where_clause.py +0 -0
  60. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/ai/__init__.py +0 -0
  61. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/ai/constants.py +0 -0
  62. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/ai/conversation.py +0 -0
  63. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/ai/enums.py +0 -0
  64. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/ai/execute.py +0 -0
  65. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/ai/models.py +0 -0
  66. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/ai/prompts.py +0 -0
  67. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/ai/providers/__init__.py +0 -0
  68. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/ai/providers/anthropic.py +0 -0
  69. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/ai/providers/base.py +0 -0
  70. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/ai/providers/google.py +0 -0
  71. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/ai/providers/openai.py +0 -0
  72. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/ai/providers/utils.py +0 -0
  73. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/authoring/__init__.py +0 -0
  74. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/__init__.py +0 -0
  75. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/constants.py +0 -0
  76. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/env_processor.py +0 -0
  77. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/environment_helpers.py +0 -0
  78. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/ergonomics.py +0 -0
  79. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/exceptions.py +0 -0
  80. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/graph_models.py +0 -0
  81. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/internal.py +0 -0
  82. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/models/__init__.py +0 -0
  83. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/models/build_environment.py +0 -0
  84. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/models/core.py +0 -0
  85. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/models/datasource.py +0 -0
  86. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/models/execute.py +0 -0
  87. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/optimizations/__init__.py +0 -0
  88. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/optimizations/base_optimization.py +0 -0
  89. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/optimizations/inline_datasource.py +0 -0
  90. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  91. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/__init__.py +0 -0
  92. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/discovery_node_factory.py +0 -0
  93. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/discovery_utility.py +0 -0
  94. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/discovery_validation.py +0 -0
  95. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/graph_utils.py +0 -0
  96. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/node_generators/__init__.py +0 -0
  97. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/node_generators/constant_node.py +0 -0
  98. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  99. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/node_generators/group_node.py +0 -0
  100. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  101. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  102. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/node_generators/recursive_node.py +0 -0
  103. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  104. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  105. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  106. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  107. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/node_generators/select_node.py +0 -0
  108. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  109. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/node_generators/union_node.py +0 -0
  110. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  111. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/node_generators/window_node.py +0 -0
  112. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/nodes/__init__.py +0 -0
  113. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/nodes/base_node.py +0 -0
  114. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/nodes/filter_node.py +0 -0
  115. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/nodes/group_node.py +0 -0
  116. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/nodes/merge_node.py +0 -0
  117. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/nodes/recursive_node.py +0 -0
  118. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  119. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/nodes/union_node.py +0 -0
  120. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  121. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/nodes/window_node.py +0 -0
  122. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/processing/utility.py +0 -0
  123. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/query_processor.py +0 -0
  124. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/statements/__init__.py +0 -0
  125. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/statements/build.py +0 -0
  126. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/statements/common.py +0 -0
  127. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/statements/execute.py +0 -0
  128. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/utility.py +0 -0
  129. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/validation/__init__.py +0 -0
  130. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/validation/common.py +0 -0
  131. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/validation/concept.py +0 -0
  132. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/validation/datasource.py +0 -0
  133. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/validation/environment.py +0 -0
  134. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/core/validation/fix.py +0 -0
  135. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/dialect/__init__.py +0 -0
  136. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/dialect/bigquery.py +0 -0
  137. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/dialect/common.py +0 -0
  138. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/dialect/config.py +0 -0
  139. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/dialect/dataframe.py +0 -0
  140. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/dialect/duckdb.py +0 -0
  141. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/dialect/enums.py +0 -0
  142. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/dialect/metadata.py +0 -0
  143. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/dialect/postgres.py +0 -0
  144. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/dialect/presto.py +0 -0
  145. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/dialect/snowflake.py +0 -0
  146. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/dialect/sql_server.py +0 -0
  147. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/engine.py +0 -0
  148. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/hooks/__init__.py +0 -0
  149. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/hooks/base_hook.py +0 -0
  150. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/hooks/graph_hook.py +0 -0
  151. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/hooks/query_debugger.py +0 -0
  152. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/metadata/__init__.py +0 -0
  153. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/parser.py +0 -0
  154. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/parsing/__init__.py +0 -0
  155. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/parsing/config.py +0 -0
  156. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/parsing/exceptions.py +0 -0
  157. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/parsing/helpers.py +0 -0
  158. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/parsing/render.py +0 -0
  159. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/py.typed +0 -0
  160. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/render.py +0 -0
  161. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/scripts/__init__.py +0 -0
  162. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/scripts/trilogy.py +0 -0
  163. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/std/__init__.py +0 -0
  164. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/std/color.preql +0 -0
  165. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/std/date.preql +0 -0
  166. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/std/display.preql +0 -0
  167. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/std/geography.preql +0 -0
  168. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/std/metric.preql +0 -0
  169. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/std/money.preql +0 -0
  170. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/std/net.preql +0 -0
  171. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/std/ranking.preql +0 -0
  172. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/std/report.preql +0 -0
  173. {pytrilogy-0.0.3.115 → pytrilogy-0.0.3.117}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.115
3
+ Version: 0.0.3.117
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.115
3
+ Version: 0.0.3.117
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):
@@ -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.115"
7
+ __version__ = "0.0.3.117"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -31,6 +31,7 @@ class Optimizations:
31
31
  constant_inlining: bool = True
32
32
  constant_inline_cutoff: int = 10
33
33
  direct_return: bool = True
34
+ hide_unused_concepts: bool = True
34
35
 
35
36
 
36
37
  @dataclass
@@ -217,6 +217,7 @@ class FunctionType(Enum):
217
217
  CONTAINS = "contains"
218
218
  TRIM = "trim"
219
219
  REPLACE = "replace"
220
+ HASH = "hash"
220
221
 
221
222
  # STRING REGEX
222
223
  REGEXP_CONTAINS = "regexp_contains"
@@ -928,6 +928,14 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
928
928
  output_type=DataType.TIMESTAMP,
929
929
  arg_count=1,
930
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
+ ),
931
939
  }
932
940
 
933
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
  ):
@@ -2133,6 +2139,89 @@ class AlignClause(Namespaced, BaseModel):
2133
2139
  )
2134
2140
 
2135
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
+
2136
2225
  class SelectLineage(Mergeable, Namespaced, BaseModel):
2137
2226
  selection: List[ConceptRef]
2138
2227
  hidden_components: set[str]
@@ -2177,15 +2266,40 @@ class SelectLineage(Mergeable, Namespaced, BaseModel):
2177
2266
  ),
2178
2267
  )
2179
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
+
2180
2292
 
2181
2293
  class MultiSelectLineage(Mergeable, ConceptArgs, Namespaced, BaseModel):
2182
2294
  selects: List[SelectLineage]
2183
2295
  align: AlignClause
2296
+
2184
2297
  namespace: str
2185
2298
  order_by: Optional[OrderBy] = None
2186
2299
  limit: Optional[int] = None
2187
2300
  where_clause: Union["WhereClause", None] = Field(default=None)
2188
2301
  having_clause: Union["HavingClause", None] = Field(default=None)
2302
+ derive: DeriveClause | None = None
2189
2303
  hidden_components: set[str]
2190
2304
 
2191
2305
  @property
@@ -2211,6 +2325,11 @@ class MultiSelectLineage(Mergeable, ConceptArgs, Namespaced, BaseModel):
2211
2325
  new = MultiSelectLineage.model_construct(
2212
2326
  selects=[s.with_merge(source, target, modifiers) for s in self.selects],
2213
2327
  align=self.align,
2328
+ derive=(
2329
+ self.derive.with_merge(source, target, modifiers)
2330
+ if self.derive
2331
+ else None
2332
+ ),
2214
2333
  namespace=self.namespace,
2215
2334
  hidden_components=self.hidden_components,
2216
2335
  order_by=(
@@ -2236,6 +2355,7 @@ class MultiSelectLineage(Mergeable, ConceptArgs, Namespaced, BaseModel):
2236
2355
  return MultiSelectLineage.model_construct(
2237
2356
  selects=[c.with_namespace(namespace) for c in self.selects],
2238
2357
  align=self.align.with_namespace(namespace),
2358
+ derive=self.derive.with_namespace(namespace) if self.derive else None,
2239
2359
  namespace=namespace,
2240
2360
  hidden_components=self.hidden_components,
2241
2361
  order_by=self.order_by.with_namespace(namespace) if self.order_by else None,
@@ -2257,6 +2377,9 @@ class MultiSelectLineage(Mergeable, ConceptArgs, Namespaced, BaseModel):
2257
2377
  output = set()
2258
2378
  for item in self.align.items:
2259
2379
  output.add(item.aligned_concept)
2380
+ if self.derive:
2381
+ for ditem in self.derive.items:
2382
+ output.add(ditem.derived_concept)
2260
2383
  return output
2261
2384
 
2262
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,
@@ -247,7 +249,7 @@ class BuildParamaterizedConceptReference(DataTyped):
247
249
  concept: BuildConcept
248
250
 
249
251
  def __str__(self):
250
- return f":{self.concept.address}"
252
+ return f":{self.concept.address.replace('.', '_')}"
251
253
 
252
254
  @property
253
255
  def safe_address(self) -> str:
@@ -1268,6 +1270,22 @@ class BuildAlignClause:
1268
1270
  items: List[BuildAlignItem]
1269
1271
 
1270
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
+
1271
1289
  @dataclass
1272
1290
  class BuildSelectLineage:
1273
1291
  selection: List[BuildConcept]
@@ -1299,12 +1317,16 @@ class BuildMultiSelectLineage(BuildConceptArgs):
1299
1317
  limit: Optional[int] = None
1300
1318
  where_clause: Union["BuildWhereClause", None] = field(default=None)
1301
1319
  having_clause: Union["BuildHavingClause", None] = field(default=None)
1320
+ derive: BuildDeriveClause | None = None
1302
1321
 
1303
1322
  @property
1304
1323
  def derived_concepts(self) -> set[str]:
1305
1324
  output = set()
1306
1325
  for item in self.align.items:
1307
1326
  output.add(item.aligned_concept)
1327
+ if self.derive:
1328
+ for ditem in self.derive.items:
1329
+ output.add(ditem.address)
1308
1330
  return output
1309
1331
 
1310
1332
  @property
@@ -1312,10 +1334,12 @@ class BuildMultiSelectLineage(BuildConceptArgs):
1312
1334
  return self.build_output_components
1313
1335
 
1314
1336
  @property
1315
- def derived_concept(self) -> set[str]:
1316
- output = set()
1317
- for item in self.align.items:
1318
- 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)
1319
1343
  return output
1320
1344
 
1321
1345
  @property
@@ -1335,6 +1359,7 @@ class BuildMultiSelectLineage(BuildConceptArgs):
1335
1359
  for c in x.concepts:
1336
1360
  if c.address in cte.output_lcl:
1337
1361
  return c
1362
+
1338
1363
  raise SyntaxError(
1339
1364
  f"Could not find upstream map for multiselect {str(concept)} on cte ({cte})"
1340
1365
  )
@@ -1961,6 +1986,28 @@ class Factory:
1961
1986
  def _build_align_clause(self, base: AlignClause) -> BuildAlignClause:
1962
1987
  return BuildAlignClause(items=[self._build_align_item(x) for x in base.items])
1963
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
+
1964
2011
  @build.register
1965
2012
  def _(self, base: RowsetItem) -> BuildRowsetItem:
1966
2013
  return self._build_rowset_item(base)
@@ -2162,6 +2209,7 @@ class Factory:
2162
2209
  selects=base.selects,
2163
2210
  grain=final_grain,
2164
2211
  align=factory.build(base.align),
2212
+ derive=factory.build(base.derive) if base.derive else None,
2165
2213
  # self.align.with_select_context(
2166
2214
  # local_build_cache, self.grain, environment
2167
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()
@@ -496,6 +496,8 @@ def _search_concepts(
496
496
  conditions=conditions,
497
497
  )
498
498
  partial: set[str] = set()
499
+ virtual: set[str] = set()
500
+ complete = ValidationResult.INCOMPLETE
499
501
  while context.incomplete:
500
502
 
501
503
  priority_concept = get_priority_concept(
@@ -7,7 +7,7 @@ from trilogy.core.models.build_environment import BuildEnvironment
7
7
  from trilogy.core.processing.node_generators.common import (
8
8
  resolve_function_parent_concepts,
9
9
  )
10
- from trilogy.core.processing.nodes import History, StrategyNode
10
+ from trilogy.core.processing.nodes import ConstantNode, History, StrategyNode
11
11
  from trilogy.utility import unique
12
12
 
13
13
  LOGGER_PREFIX = "[GEN_BASIC_NODE]"
@@ -51,11 +51,14 @@ def gen_basic_node(
51
51
  )
52
52
  synonyms: list[BuildConcept] = []
53
53
  ignored_optional: set[str] = set()
54
- assert isinstance(concept.lineage, BuildFunction)
54
+
55
55
  # when we are getting an attribute, if there is anything else
56
56
  # that is an attribute of the same struct in local optional
57
57
  # select that value for discovery as well
58
- if concept.lineage.operator == FunctionType.ATTR_ACCESS:
58
+ if (
59
+ isinstance(concept.lineage, BuildFunction)
60
+ and concept.lineage.operator == FunctionType.ATTR_ACCESS
61
+ ):
59
62
  logger.info(
60
63
  f"{depth_prefix}{LOGGER_PREFIX} checking for synonyms for attribute access"
61
64
  )
@@ -106,20 +109,28 @@ def gen_basic_node(
106
109
  logger.info(
107
110
  f"{depth_prefix}{LOGGER_PREFIX} Fetching parents {[x.address for x in all_parents]}"
108
111
  )
109
- parent_node: StrategyNode | None = source_concepts(
110
- mandatory_list=all_parents,
111
- environment=environment,
112
- g=g,
113
- depth=depth + 1,
114
- history=history,
115
- conditions=conditions,
116
- )
112
+ if all_parents:
113
+ parent_node: StrategyNode | None = source_concepts(
114
+ mandatory_list=all_parents,
115
+ environment=environment,
116
+ g=g,
117
+ depth=depth + 1,
118
+ history=history,
119
+ conditions=conditions,
120
+ )
117
121
 
118
- if not parent_node:
119
- logger.info(
120
- f"{depth_prefix}{LOGGER_PREFIX} No basic node could be generated for {concept}"
122
+ if not parent_node:
123
+ logger.info(
124
+ f"{depth_prefix}{LOGGER_PREFIX} No basic node could be generated for {concept}"
125
+ )
126
+ return None
127
+ else:
128
+ return ConstantNode(
129
+ input_concepts=[],
130
+ output_concepts=[concept],
131
+ environment=environment,
132
+ depth=depth,
121
133
  )
122
- return None
123
134
  if parent_node.source_type != SourceType.CONSTANT:
124
135
  parent_node.source_type = SourceType.BASIC
125
136
  parent_node.add_output_concept(concept)
@@ -4,6 +4,7 @@ from typing import Callable, List, Tuple
4
4
  from trilogy.core.enums import Derivation, Purpose
5
5
  from trilogy.core.models.build import (
6
6
  BuildAggregateWrapper,
7
+ BuildComparison,
7
8
  BuildConcept,
8
9
  BuildFilterItem,
9
10
  BuildFunction,
@@ -26,7 +27,9 @@ FUNCTION_TYPES = (BuildFunction,)
26
27
  def resolve_function_parent_concepts(
27
28
  concept: BuildConcept, environment: BuildEnvironment
28
29
  ) -> List[BuildConcept]:
29
- if not isinstance(concept.lineage, (*FUNCTION_TYPES, *AGGREGATE_TYPES)):
30
+ if not isinstance(
31
+ concept.lineage, (*FUNCTION_TYPES, *AGGREGATE_TYPES, BuildComparison)
32
+ ):
30
33
  raise ValueError(
31
34
  f"Concept {concept} lineage is not function or aggregate, is {type(concept.lineage)}"
32
35
  )
@@ -157,19 +157,19 @@ def gen_multiselect_node(
157
157
  possible_joins = concept_to_relevant_joins(additional_relevant)
158
158
  if not local_optional:
159
159
  logger.info(
160
- f"{padding(depth)}{LOGGER_PREFIX} no enrichment required for rowset node; exiting early"
160
+ f"{padding(depth)}{LOGGER_PREFIX} no enrichment required for multiselect node; exiting early"
161
161
  )
162
162
  return node
163
163
  if not possible_joins:
164
164
  logger.info(
165
- f"{padding(depth)}{LOGGER_PREFIX} no possible joins for rowset node; exiting early"
165
+ f"{padding(depth)}{LOGGER_PREFIX} no possible joins for multiselect node; exiting early"
166
166
  )
167
167
  return node
168
168
  if all(
169
169
  [x.address in [y.address for y in node.output_concepts] for x in local_optional]
170
170
  ):
171
171
  logger.info(
172
- f"{padding(depth)}{LOGGER_PREFIX} all enriched concepts returned from base rowset node; exiting early"
172
+ f"{padding(depth)}{LOGGER_PREFIX} all enriched concepts returned from base multiselect node; exiting early"
173
173
  )
174
174
  return node
175
175
  logger.info(
@@ -21,6 +21,7 @@ from trilogy.core.models.author import (
21
21
  Concept,
22
22
  ConceptRef,
23
23
  CustomType,
24
+ DeriveClause,
24
25
  Expr,
25
26
  FilterItem,
26
27
  Function,
@@ -352,11 +353,13 @@ class MultiSelectStatement(HasUUID, SelectTypeMixin, BaseModel):
352
353
  local_concepts: Annotated[
353
354
  EnvironmentConceptDict, PlainValidator(validate_concepts)
354
355
  ] = Field(default_factory=EnvironmentConceptDict)
356
+ derive: DeriveClause | None = None
355
357
 
356
358
  def as_lineage(self, environment: Environment):
357
359
  return MultiSelectLineage(
358
360
  selects=[x.as_lineage(environment) for x in self.selects],
359
361
  align=self.align,
362
+ derive=self.derive,
360
363
  namespace=self.namespace,
361
364
  # derived_concepts = self.derived_concepts,
362
365
  limit=self.limit,
@@ -176,6 +176,20 @@ def struct_arg(args):
176
176
  return [f"{x[1]}: {x[0]}" for x in zip(args[::2], args[1::2])]
177
177
 
178
178
 
179
+ def hash_from_args(val, hash_type):
180
+ hash_type = hash_type[1:-1]
181
+ if hash_type.lower() == "md5":
182
+ return f"md5({val})"
183
+ elif hash_type.lower() == "sha1":
184
+ return f"sha1({val})"
185
+ elif hash_type.lower() == "sha256":
186
+ return f"sha256({val})"
187
+ elif hash_type.lower() == "sha512":
188
+ return f"sha512({val})"
189
+ else:
190
+ raise ValueError(f"Unsupported hash type: {hash_type}")
191
+
192
+
179
193
  FUNCTION_MAP = {
180
194
  # generic types
181
195
  FunctionType.ALIAS: lambda x: f"{x[0]}",
@@ -260,6 +274,7 @@ FUNCTION_MAP = {
260
274
  FunctionType.REGEXP_REPLACE: lambda x: f"REGEXP_REPLACE({x[0]},{x[1]}, {x[2]})",
261
275
  FunctionType.TRIM: lambda x: f"TRIM({x[0]})",
262
276
  FunctionType.REPLACE: lambda x: f"REPLACE({x[0]},{x[1]},{x[2]})",
277
+ FunctionType.HASH: lambda x: hash_from_args(x[0], x[1]),
263
278
  # FunctionType.NOT_LIKE: lambda x: f" CASE WHEN {x[0]} like {x[1]} THEN 0 ELSE 1 END",
264
279
  # date types
265
280
  FunctionType.DATE_TRUNCATE: lambda x: f"date_trunc({x[0]},{x[1]})",
@@ -484,7 +499,20 @@ class BaseDialect:
484
499
  elif isinstance(c.lineage, BuildRowsetItem):
485
500
  rval = f"{self.render_concept_sql(c.lineage.content, cte=cte, alias=False, raise_invalid=raise_invalid)}"
486
501
  elif isinstance(c.lineage, BuildMultiSelectLineage):
487
- rval = f"{self.render_concept_sql(c.lineage.find_source(c, cte), cte=cte, alias=False, raise_invalid=raise_invalid)}"
502
+ if c.address in c.lineage.calculated_derivations:
503
+ assert c.lineage.derive is not None
504
+ for x in c.lineage.derive.items:
505
+ if x.address == c.address:
506
+ rval = self.render_expr(
507
+ x.expr,
508
+ cte=cte,
509
+ raise_invalid=raise_invalid,
510
+ )
511
+ break
512
+ else:
513
+ rval = f"{self.render_concept_sql(c.lineage.find_source(c, cte), cte=cte, alias=False, raise_invalid=raise_invalid)}"
514
+ elif isinstance(c.lineage, BuildComparison):
515
+ rval = f"{self.render_expr(c.lineage.left, cte=cte, raise_invalid=raise_invalid)} {c.lineage.operator.value} {self.render_expr(c.lineage.right, cte=cte, raise_invalid=raise_invalid)}"
488
516
  elif isinstance(c.lineage, AGGREGATE_ITEMS):
489
517
  args = [
490
518
  self.render_expr(v, cte) # , alias=False)
@@ -816,7 +844,7 @@ class BaseDialect:
816
844
  if self.rendering.parameters:
817
845
  if e.concept.namespace == DEFAULT_NAMESPACE:
818
846
  return f":{e.concept.name}"
819
- return f":{e.concept.address}"
847
+ return f":{e.concept.address.replace('.', '_')}"
820
848
  elif e.concept.lineage:
821
849
  return self.render_expr(e.concept.lineage, cte=cte, cte_map=cte_map)
822
850
  return f"{self.QUOTE_CHARACTER}{e.concept.address}{self.QUOTE_CHARACTER}"