pytrilogy 0.3.331__tar.gz → 0.3.332__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.
Files changed (409) hide show
  1. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/PKG-INFO +1 -1
  2. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/__init__.py +1 -1
  3. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/authoring/__init__.py +2 -0
  4. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/models/execute.py +17 -1
  5. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/optimizations/union_dim_pushdown.py +35 -5
  6. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +87 -29
  7. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/strategy_builder.py +13 -1
  8. pytrilogy-0.3.332/trilogy/core/processing/v4_node_generators/basic.py +104 -0
  9. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/query_processor.py +70 -18
  10. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/statements/author.py +24 -1
  11. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/statements/execute.py +10 -0
  12. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/base.py +18 -0
  13. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/python_source.py +7 -0
  14. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/execution/state/AGENTS.md +6 -0
  15. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/execution/state/__init__.py +2 -0
  16. pytrilogy-0.3.332/trilogy/execution/state/isolation.py +39 -0
  17. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/execution/state/partitions.py +19 -19
  18. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/execution/state/state_store.py +10 -14
  19. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/execution/state/watermarks.py +72 -30
  20. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/executor.py +104 -0
  21. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parser.py +2 -0
  22. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/parse_engine_v2.py +4 -3
  23. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/render.py +79 -1
  24. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/trilogy.lark +6 -0
  25. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/hydration.py +24 -1
  26. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/chart_rules.py +9 -2
  27. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/operational_rules.py +53 -0
  28. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/statement_planner.py +3 -0
  29. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/statement_plans.py +32 -1
  30. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/syntax.py +2 -0
  31. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/rendering/altair_renderer.py +17 -9
  32. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/agent_info_docs/content.py +19 -0
  33. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/common.py +18 -6
  34. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/Cargo.lock +1 -1
  35. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/Cargo.toml +1 -1
  36. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/src/trilogy.pest +3 -0
  37. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/display.py +2 -0
  38. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/display_parallel.py +67 -39
  39. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/parallel_execution.py +11 -3
  40. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/refresh.py +1 -1
  41. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/single_execution.py +1 -1
  42. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/source.py +2 -5
  43. pytrilogy-0.3.331/trilogy/core/processing/v4_node_generators/basic.py +0 -53
  44. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/.scripts/__init__.py +0 -0
  45. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/.scripts/build_backend.py +0 -0
  46. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/.scripts/sync_version.py +0 -0
  47. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/LICENSE.md +0 -0
  48. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/README.md +0 -0
  49. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/pyproject.toml +0 -0
  50. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/ai/README.md +0 -0
  51. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/ai/__init__.py +0 -0
  52. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/ai/constants.py +0 -0
  53. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/ai/conversation.py +0 -0
  54. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/ai/enums.py +0 -0
  55. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/ai/execute.py +0 -0
  56. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/ai/models.py +0 -0
  57. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/ai/prompts.py +0 -0
  58. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/ai/providers/__init__.py +0 -0
  59. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/ai/providers/anthropic.py +0 -0
  60. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/ai/providers/base.py +0 -0
  61. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/ai/providers/deepseek.py +0 -0
  62. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/ai/providers/google.py +0 -0
  63. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/ai/providers/openai.py +0 -0
  64. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/ai/providers/openrouter.py +0 -0
  65. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/ai/providers/utils.py +0 -0
  66. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/ai/syntax_examples.py +0 -0
  67. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/authoring/README.md +0 -0
  68. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/constants.py +0 -0
  69. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/README.md +0 -0
  70. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/__init__.py +0 -0
  71. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/constants.py +0 -0
  72. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/domain_graph.py +0 -0
  73. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/domain_validation.py +0 -0
  74. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/enums.py +0 -0
  75. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/env_processor.py +0 -0
  76. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/environment_helpers.py +0 -0
  77. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/ergonomics.py +0 -0
  78. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/exceptions.py +0 -0
  79. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/fingerprint.py +0 -0
  80. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/functions.py +0 -0
  81. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/graph.py +0 -0
  82. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/graph_models.py +0 -0
  83. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/having_normalization.py +0 -0
  84. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/internal.py +0 -0
  85. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/models/__init__.py +0 -0
  86. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/models/author.py +0 -0
  87. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/models/build.py +0 -0
  88. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/models/build_environment.py +0 -0
  89. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/models/core.py +0 -0
  90. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/models/datasource.py +0 -0
  91. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/models/environment.py +0 -0
  92. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/optimization.py +0 -0
  93. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/optimizations/__init__.py +0 -0
  94. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/optimizations/base_optimization.py +0 -0
  95. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/optimizations/collapse_single_parent.py +0 -0
  96. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/optimizations/filtered_aggregate.py +0 -0
  97. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/optimizations/filtered_count_join.py +0 -0
  98. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/optimizations/full_join_lowering.py +0 -0
  99. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/optimizations/hide_unused_concept.py +0 -0
  100. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/optimizations/inline_datasource.py +0 -0
  101. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/optimizations/join_hoist.py +0 -0
  102. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/optimizations/join_upgrade.py +0 -0
  103. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/optimizations/keyless_full_join.py +0 -0
  104. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/optimizations/merge_irrelevant_group_by.py +0 -0
  105. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/optimizations/null_safe_join.py +0 -0
  106. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/optimizations/order_inner_joins.py +0 -0
  107. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  108. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/optimizations/semi_join_pushdown.py +0 -0
  109. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/optimizations/strip_redundant_not_null.py +0 -0
  110. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/optimizations/utils.py +0 -0
  111. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/optimizations/value_set_join_upgrade.py +0 -0
  112. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/README.md +0 -0
  113. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/VIRTUAL_UNNEST.md +0 -0
  114. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/__init__.py +0 -0
  115. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/aggregate_rollup.py +0 -0
  116. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/concept_strategies_v4.py +0 -0
  117. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/condition_utility.py +0 -0
  118. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/constants.py +0 -0
  119. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/discovery_utility.py +0 -0
  120. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/discovery_validation.py +0 -0
  121. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/grain_utility.py +0 -0
  122. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/join_resolution.py +0 -0
  123. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/model_ambiguity.py +0 -0
  124. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/node_generators/__init__.py +0 -0
  125. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/node_generators/common.py +0 -0
  126. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/node_generators/presence_probe.py +0 -0
  127. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  128. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/node_generators/select_helpers/condition_routing.py +0 -0
  129. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/node_generators/select_helpers/datasource_nodes.py +0 -0
  130. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/node_generators/select_helpers/source_scoring.py +0 -0
  131. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  132. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/node_generators/select_node.py +0 -0
  133. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/README.md +0 -0
  134. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/__init__.py +0 -0
  135. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/base_node.py +0 -0
  136. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/filter_node.py +0 -0
  137. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/group_node.py +0 -0
  138. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/merge_node.py +0 -0
  139. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/recursive_node.py +0 -0
  140. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  141. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/subselect_node.py +0 -0
  142. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/union_node.py +0 -0
  143. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  144. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/window_node.py +0 -0
  145. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/rowset_islanding.py +0 -0
  146. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/utility.py +0 -0
  147. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/__init__.py +0 -0
  148. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/concept_graph.py +0 -0
  149. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/condition_injection.py +0 -0
  150. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/condition_placement.py +0 -0
  151. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/constants.py +0 -0
  152. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/edges.py +0 -0
  153. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/functional_dependency.py +0 -0
  154. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/group_behaviors.py +0 -0
  155. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/group_graph.py +0 -0
  156. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/group_rules.py +0 -0
  157. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/history.py +0 -0
  158. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/models.py +0 -0
  159. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/network_build.py +0 -0
  160. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/network_coalescing.py +0 -0
  161. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/network_model.py +0 -0
  162. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/network_obligations.py +0 -0
  163. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/network_search.py +0 -0
  164. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/network_topology.py +0 -0
  165. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/projection.py +0 -0
  166. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/source_planning.py +0 -0
  167. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/staged_where.py +0 -0
  168. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/__init__.py +0 -0
  169. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/aggregate.py +0 -0
  170. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/common.py +0 -0
  171. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/condition_sources.py +0 -0
  172. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/constant.py +0 -0
  173. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/dispatch.py +0 -0
  174. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/filter.py +0 -0
  175. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/group_to.py +0 -0
  176. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/multiselect.py +0 -0
  177. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/nested_select.py +0 -0
  178. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/recursive.py +0 -0
  179. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/root.py +0 -0
  180. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/rowset.py +0 -0
  181. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/subselect.py +0 -0
  182. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/union.py +0 -0
  183. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/union_select.py +0 -0
  184. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/unnest.py +0 -0
  185. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/window.py +0 -0
  186. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/scope_diagnostics.py +0 -0
  187. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/statements/README.md +0 -0
  188. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/statements/__init__.py +0 -0
  189. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/statements/build.py +0 -0
  190. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/statements/common.py +0 -0
  191. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/table_processor.py +0 -0
  192. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/utility.py +0 -0
  193. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/validation/README.md +0 -0
  194. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/validation/__init__.py +0 -0
  195. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/validation/common.py +0 -0
  196. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/validation/concept.py +0 -0
  197. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/validation/datasource.py +0 -0
  198. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/validation/environment.py +0 -0
  199. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/validation/fix.py +0 -0
  200. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/validation/rows.py +0 -0
  201. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/core/where_scope_normalization.py +0 -0
  202. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/__init__.py +0 -0
  203. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/bigquery.py +0 -0
  204. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/bigquery_engine.py +0 -0
  205. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/bigquery_persist.py +0 -0
  206. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/bigquery_staging.py +0 -0
  207. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/cancel.py +0 -0
  208. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/clickhouse.py +0 -0
  209. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/clickhouse_chdb.py +0 -0
  210. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/common.py +0 -0
  211. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/config.py +0 -0
  212. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/dataframe.py +0 -0
  213. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/duckdb.py +0 -0
  214. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/duckdb_uv.py +0 -0
  215. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/enums.py +0 -0
  216. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/metadata.py +0 -0
  217. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/mock.py +0 -0
  218. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/mysql.py +0 -0
  219. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/postgres.py +0 -0
  220. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/presto.py +0 -0
  221. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/results.py +0 -0
  222. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/snowflake.py +0 -0
  223. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/source_pushdown.py +0 -0
  224. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/sql_server.py +0 -0
  225. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/dialect/sqlite.py +0 -0
  226. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/engine.py +0 -0
  227. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/execution/__init__.py +0 -0
  228. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/execution/config.py +0 -0
  229. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/execution/envs.py +0 -0
  230. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/execution/model_fingerprint.py +0 -0
  231. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/execution/report.py +0 -0
  232. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/execution/state/CLAUDE.md +0 -0
  233. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/execution/state/cache.py +0 -0
  234. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/execution/state/exceptions.py +0 -0
  235. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/execution/state/persistence.py +0 -0
  236. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/execution/state/phases.py +0 -0
  237. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/execution/state/snapshot.py +0 -0
  238. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/hooks/__init__.py +0 -0
  239. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/hooks/base_hook.py +0 -0
  240. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/hooks/graph_hook.py +0 -0
  241. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/hooks/query_debugger.py +0 -0
  242. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/io/__init__.py +0 -0
  243. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/io/adapters.py +0 -0
  244. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/io/arrow.py +0 -0
  245. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/io/click_support.py +0 -0
  246. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/io/contract.py +0 -0
  247. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/io/describe.py +0 -0
  248. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/io/errors.py +0 -0
  249. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/io/runner.py +0 -0
  250. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/io/sinks.py +0 -0
  251. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/metadata/__init__.py +0 -0
  252. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/README.md +0 -0
  253. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/__init__.py +0 -0
  254. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/common.py +0 -0
  255. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/config.py +0 -0
  256. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/exceptions.py +0 -0
  257. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/helpers.py +0 -0
  258. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/pretty.py +0 -0
  259. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/README.md +0 -0
  260. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/__init__.py +0 -0
  261. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/concept_factory.py +0 -0
  262. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/concept_syntax.py +0 -0
  263. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/errors.py +0 -0
  264. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/function_syntax.py +0 -0
  265. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/import_service.py +0 -0
  266. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/lark_backend.py +0 -0
  267. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/model.py +0 -0
  268. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/pest_backend.py +0 -0
  269. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rowset_semantics.py +0 -0
  270. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/__init__.py +0 -0
  271. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/concept_rules.py +0 -0
  272. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/conditional_rules.py +0 -0
  273. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/datasource_rules.py +0 -0
  274. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/expression_rules.py +0 -0
  275. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/function_definition_rules.py +0 -0
  276. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/function_rules.py +0 -0
  277. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/import_rules.py +0 -0
  278. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/merge_rules.py +0 -0
  279. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/multiselect_rules.py +0 -0
  280. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/order_rules.py +0 -0
  281. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/persist_rules.py +0 -0
  282. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/rawsql_rules.py +0 -0
  283. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/rowset_rules.py +0 -0
  284. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/select_rules.py +0 -0
  285. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/select_statement_rules.py +0 -0
  286. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/statement_rules.py +0 -0
  287. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/subselect_rules.py +0 -0
  288. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/token_rules.py +0 -0
  289. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/tvf_rules.py +0 -0
  290. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/type_rules.py +0 -0
  291. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules_context.py +0 -0
  292. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/select_finalize.py +0 -0
  293. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/semantic_scope.py +0 -0
  294. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/semantic_state.py +0 -0
  295. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/parsing/v2/symbols.py +0 -0
  296. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/py.typed +0 -0
  297. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/render.py +0 -0
  298. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/rendering/__init__.py +0 -0
  299. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/rendering/base.py +0 -0
  300. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/rendering/chart_theme.py +0 -0
  301. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/rendering/rich_types.py +0 -0
  302. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/rendering/terminal_renderer.py +0 -0
  303. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/rendering/theme.py +0 -0
  304. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/report/__init__.py +0 -0
  305. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/report/backends/__init__.py +0 -0
  306. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/report/backends/base.py +0 -0
  307. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/report/backends/html.py +0 -0
  308. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/report/backends/png.py +0 -0
  309. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/report/charts.py +0 -0
  310. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/report/document.py +0 -0
  311. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/report/runner.py +0 -0
  312. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/report/tables.py +0 -0
  313. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/AGENTS.md +0 -0
  314. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/CLAUDE.md +0 -0
  315. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/README.md +0 -0
  316. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/__init__.py +0 -0
  317. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/agent.py +0 -0
  318. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/agent_info.py +0 -0
  319. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/agent_info_docs/__init__.py +0 -0
  320. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/agent_info_docs/cli.py +0 -0
  321. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/agent_info_docs/directory.py +0 -0
  322. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/agent_sessions.py +0 -0
  323. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/agent_sql_tools.py +0 -0
  324. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/agent_tools.py +0 -0
  325. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/click_utils.py +0 -0
  326. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/cloud.py +0 -0
  327. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/cloud_models.py +0 -0
  328. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/database.py +0 -0
  329. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/README.md +0 -0
  330. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/build.sh +0 -0
  331. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/examples/basic_usage.rs +0 -0
  332. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/src/directory_resolver.rs +0 -0
  333. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/src/graph.rs +0 -0
  334. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/src/lib.rs +0 -0
  335. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/src/main.rs +0 -0
  336. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/src/network_search.rs +0 -0
  337. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/src/parser.rs +0 -0
  338. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/src/python_bindings.rs +0 -0
  339. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/src/resolver.rs +0 -0
  340. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/src/trilogy_parser.rs +0 -0
  341. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/tests/base.preql +0 -0
  342. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/tests/cli_integration.rs +0 -0
  343. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/tests/customer.preql +0 -0
  344. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/tests/main.preql +0 -0
  345. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/tests/orders.preql +0 -0
  346. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/tests/public_api.rs +0 -0
  347. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/tests/test_data/base.preql +0 -0
  348. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency/tests/test_data/consumer.preql +0 -0
  349. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/dependency.py +0 -0
  350. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/display_core.py +0 -0
  351. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/display_execution.py +0 -0
  352. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/display_ingest.py +0 -0
  353. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/display_init.py +0 -0
  354. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/display_models.py +0 -0
  355. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/display_refresh.py +0 -0
  356. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/display_validation.py +0 -0
  357. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/env_commands.py +0 -0
  358. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/environment.py +0 -0
  359. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/explore.py +0 -0
  360. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/explore_seen.py +0 -0
  361. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/file.py +0 -0
  362. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/file_helpers/__init__.py +0 -0
  363. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/file_helpers/backends.py +0 -0
  364. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/file_helpers/preql_description.py +0 -0
  365. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/file_helpers/preql_validation.py +0 -0
  366. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/fmt.py +0 -0
  367. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/ingest.py +0 -0
  368. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/ingest_helpers/__init__.py +0 -0
  369. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/ingest_helpers/fk_inference.py +0 -0
  370. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/ingest_helpers/foreign_keys.py +0 -0
  371. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/ingest_helpers/formatting.py +0 -0
  372. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/ingest_helpers/introspection.py +0 -0
  373. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/ingest_helpers/typing.py +0 -0
  374. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/init.py +0 -0
  375. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/plan.py +0 -0
  376. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/project_config.py +0 -0
  377. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/public.py +0 -0
  378. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/render.py +0 -0
  379. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/run.py +0 -0
  380. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/serve.py +0 -0
  381. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/serve_helpers/__init__.py +0 -0
  382. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/serve_helpers/connection_spec.py +0 -0
  383. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/serve_helpers/file_discovery.py +0 -0
  384. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/serve_helpers/index_generation.py +0 -0
  385. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/serve_helpers/jobs.py +0 -0
  386. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/serve_helpers/models.py +0 -0
  387. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/serve_helpers/state_cache.py +0 -0
  388. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/serve_helpers/state_computation.py +0 -0
  389. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/serve_helpers/studio_bundle.py +0 -0
  390. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/source_identity.py +0 -0
  391. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/state.py +0 -0
  392. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/testing.py +0 -0
  393. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/trilogy.py +0 -0
  394. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/scripts/validate_agent.py +0 -0
  395. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/staging.py +0 -0
  396. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/std/__init__.py +0 -0
  397. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/std/chart.preql +0 -0
  398. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/std/color.preql +0 -0
  399. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/std/currency.preql +0 -0
  400. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/std/date.preql +0 -0
  401. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/std/display.preql +0 -0
  402. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/std/geography.preql +0 -0
  403. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/std/metric.preql +0 -0
  404. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/std/money.preql +0 -0
  405. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/std/net.preql +0 -0
  406. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/std/ranking.preql +0 -0
  407. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/std/report.preql +0 -0
  408. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/std/semantic.preql +0 -0
  409. {pytrilogy-0.3.331 → pytrilogy-0.3.332}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.3.331
3
+ Version: 0.3.332
4
4
  Classifier: Programming Language :: Python
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Classifier: Programming Language :: Python :: 3.9
@@ -7,7 +7,7 @@ if TYPE_CHECKING:
7
7
  from trilogy.executor import Executor
8
8
  from trilogy.parser import parse
9
9
 
10
- __version__ = "0.3.331"
10
+ __version__ = "0.3.332"
11
11
 
12
12
  __all__ = [
13
13
  "CONFIG",
@@ -55,6 +55,7 @@ from trilogy.core.models.datasource import Address, Datasource, DatasourceMetada
55
55
  from trilogy.core.models.environment import DictImportResolver, Environment
56
56
  from trilogy.core.statements.author import (
57
57
  STATEMENT_TYPES,
58
+ CallStatement,
58
59
  ConceptDeclarationStatement,
59
60
  ConceptTransform,
60
61
  CopyStatement,
@@ -89,6 +90,7 @@ __all__ = [
89
90
  "ArrayType",
90
91
  # trilogy.core.enums
91
92
  "BooleanOperator",
93
+ "CallStatement",
92
94
  "CaseElse",
93
95
  "CaseWhen",
94
96
  "Comment",
@@ -1830,11 +1830,27 @@ class UnionCTE:
1830
1830
 
1831
1831
  @property
1832
1832
  def group_to_grain(self) -> bool:
1833
+ """A union never dedupes itself — only its consumer can decide.
1834
+
1835
+ A UNION ALL whose projection drops its arms' grain keys does hold
1836
+ duplicate rows against its own declared grain, so this is NOT the
1837
+ "already a unique set" it looks like. But the duplicates are only noise
1838
+ to a consumer joining the union as a key set; to a consumer aggregating
1839
+ it they are the data. `select species, sum(dbh)` over two tree-grain
1840
+ partitions projects (species, dbh) and must keep all three of three
1841
+ identically-valued trees — deduping here silently under-counts the sum.
1842
+
1843
+ The union cannot tell those apart, so the obligation sits with whoever
1844
+ reads it at a coarser grain: that consumer must group. See
1845
+ `_grain_claim_needs_group` in v4_node_generators/basic.py, which is the
1846
+ projection-shaped case of exactly that.
1847
+ """
1833
1848
  return False
1834
1849
 
1835
1850
  @property
1836
1851
  def group_concepts(self) -> list[BuildConcept]:
1837
- # unions should always be on unique sets
1852
+ # Nothing to group by: see group_to_grain — deduping is the consumer's
1853
+ # obligation, never the union's.
1838
1854
  return []
1839
1855
 
1840
1856
  def __add__(self, other):
@@ -192,6 +192,23 @@ def _datasource_matches_raw_id(
192
192
  return isinstance(base, BuildDatasource) and base.identifier == source_id
193
193
 
194
194
 
195
+ def _derives_from(node: CTE | UnionCTE, container_name: str) -> bool:
196
+ """``node`` IS ``container_name`` or reads from it, at any depth."""
197
+ seen: set[str] = set()
198
+ stack: list[CTE | UnionCTE] = [node]
199
+ while stack:
200
+ current = stack.pop()
201
+ if current.name == container_name:
202
+ return True
203
+ if current.name in seen:
204
+ continue
205
+ seen.add(current.name)
206
+ stack.extend(current.dependency_nodes())
207
+ if isinstance(current, UnionCTE):
208
+ stack.extend(current.internal_ctes)
209
+ return False
210
+
211
+
195
212
  def _dim_local_atoms(
196
213
  cte: CTE,
197
214
  dim_qds: BuildDatasource | QueryDatasource,
@@ -604,12 +621,25 @@ class UnionDimPushdown(OptimizationRule):
604
621
  return needed.issubset(out)
605
622
 
606
623
  def _resolve_push_context(
607
- self, consumers: list[CTE], d: _DimDescriptor
624
+ self, consumers: list[CTE], d: _DimDescriptor, container: CTE | UnionCTE
608
625
  ) -> _PushContext | None:
609
626
  for c in consumers:
610
627
  found = _find_dim_cte_for_qds(c, d.join_qds_id)
611
- if found is not None and self._dim_cte_exposes(found, d):
612
- return _PushContext(dim_cte=found, source_consumer=c)
628
+ if found is None or not self._dim_cte_exposes(found, d):
629
+ continue
630
+ # A "dim" carved OUT of the container (a single-key projection of the
631
+ # union, which the planner peels when the union is the only source of
632
+ # that key) cannot be computed before it. Pushing it in would make the
633
+ # container's branches depend on a CTE that depends on the container —
634
+ # `reorder_ctes` then fails the whole query with "CTE dependency graph
635
+ # contains a cycle".
636
+ if _derives_from(found, container.name):
637
+ self.log(
638
+ f"Skipping dim {d.dim_qds.identifier}: its CTE {found.name} "
639
+ f"is derived from {container.name}"
640
+ )
641
+ continue
642
+ return _PushContext(dim_cte=found, source_consumer=c)
613
643
  return None
614
644
 
615
645
  def _can_push_into_branch(self, branch: CTE, d: _DimDescriptor) -> bool:
@@ -634,7 +664,7 @@ class UnionDimPushdown(OptimizationRule):
634
664
  if not d.strip_safe and not d.where_atoms:
635
665
  return False
636
666
 
637
- context = self._resolve_push_context(consumers, d)
667
+ context = self._resolve_push_context(consumers, d, union)
638
668
  if context is None:
639
669
  return False
640
670
 
@@ -707,7 +737,7 @@ class UnionDimPushdown(OptimizationRule):
707
737
  """
708
738
  if not d.strip_safe or not d.where_atoms:
709
739
  return False
710
- context = self._resolve_push_context(consumers, d)
740
+ context = self._resolve_push_context(consumers, d, target)
711
741
  if context is None:
712
742
  return False
713
743
  if not self._can_push_into_branch(target, d):
@@ -1,5 +1,4 @@
1
1
  from collections import defaultdict
2
- from itertools import product
3
2
 
4
3
  from trilogy.core.enums import (
5
4
  AddressType,
@@ -76,9 +75,12 @@ def _best_enum_union(
76
75
  ) -> list[list[BuildDatasource]] | None:
77
76
  """Find the best minimal covering combinations for an enum-partitioned key.
78
77
 
79
- Groups by covered enum value, enumerates one-source-per-value combos,
80
- computes the combo's actual concept overlap (minus the merge key), and
81
- keeps the highest-scoring combo per distinct overlap signature. Returning
78
+ Groups by covered enum value, then searches one-source-per-value combos via
79
+ a dynamic program over values whose state is the combo's concept overlap so
80
+ far (minus the merge key), keeping the highest-scoring combo per distinct
81
+ overlap signature. The score is separable (a per-source sum) and the
82
+ overlap is the only coupling between values, so this is exhaustive over
83
+ achievable signatures without enumerating the k^V combo product. Returning
82
84
  one combo per overlap lets parallel partitionings (e.g., sales vs.
83
85
  returns vs. dim, all keyed by the same channel enum) each contribute
84
86
  their own union datasource instead of collapsing into the single best.
@@ -100,36 +102,92 @@ def _best_enum_union(
100
102
  return None
101
103
 
102
104
  values = list(by_value.keys())
103
- merge_key_addr = {merge_key.address}
104
-
105
- best_per_overlap: dict[frozenset[str], tuple[list[BuildDatasource], int]] = {}
105
+ # A union requires at least 2 distinct sources; a single source is not a union
106
+ if len(values) < 2:
107
+ return None
106
108
 
107
- for combo in product(*[by_value[v] for v in values]):
108
- combo_list = list(combo)
109
+ cols: dict[int, frozenset[str]] = {}
110
+ scores: dict[int, int] = {}
111
+ for candidates in by_value.values():
112
+ for ds in candidates:
113
+ cols[id(ds)] = frozenset(col.concept.address for col in ds.columns)
114
+ scores[id(ds)] = _datasource_score(ds)
109
115
 
110
- # A union requires at least 2 distinct sources; a single source is not a union
111
- if len(combo_list) < 2:
116
+ # Members MAY disagree on intrinsic (~) partiality of a shared column
117
+ # (a "mixed-family" combo, e.g. web_sales + catalog/store_returns).
118
+ # Such a union is a legitimate per-channel provider of columns it binds
119
+ # complete (q05: a web return's return-site lives on web_sales), and
120
+ # union partial propagation keeps its ~-partial keys from ever outranking
121
+ # a pure family that binds them complete (q14) — so don't reject it here.
122
+ # An empty overlap beyond the merge key IS rejected, at the first step it
123
+ # appears: intersection only shrinks, so no continuation can revive it.
124
+ # Ties reproduce the old product enumeration exactly: per signature the
125
+ # winner is the first max-scoring combo in product order (`combo_key` =
126
+ # candidate index tuple, lexicographic = product order), and signatures
127
+ # order by their first-achieving combo (`min_key`, tracked over ALL combos
128
+ # reaching a signature, not just the best-scoring one).
129
+ merge_key_addr = merge_key.address
130
+ # signature -> (score, combo, combo_key, min_key)
131
+ states: dict[
132
+ frozenset[str],
133
+ tuple[int, list[BuildDatasource], tuple[int, ...], tuple[int, ...]],
134
+ ] = {}
135
+ for idx, ds in enumerate(by_value[values[0]]):
136
+ sig = cols[id(ds)] - {merge_key_addr}
137
+ if not sig:
112
138
  continue
139
+ existing = states.get(sig)
140
+ if existing is None:
141
+ states[sig] = (scores[id(ds)], [ds], (idx,), (idx,))
142
+ elif scores[id(ds)] > existing[0]:
143
+ states[sig] = (scores[id(ds)], [ds], (idx,), existing[3])
113
144
 
114
- # Require at least one shared concept beyond the merge key
115
- overlap = {col.concept.address for col in combo_list[0].columns}
116
- for ds in combo_list[1:]:
117
- overlap &= {col.concept.address for col in ds.columns}
118
- signature = frozenset(overlap - merge_key_addr)
119
- if not signature:
120
- continue
121
- # Members MAY disagree on intrinsic (~) partiality of a shared column
122
- # (a "mixed-family" combo, e.g. web_sales + catalog/store_returns).
123
- # Such a union is a legitimate per-channel provider of columns it binds
124
- # complete (q05: a web return's return-site lives on web_sales), and
125
- # union partial propagation keeps its ~-partial keys from ever outranking
126
- # a pure family that binds them complete (q14) — so don't reject it here.
127
-
128
- score = sum(_datasource_score(ds) for ds in combo_list)
129
- existing = best_per_overlap.get(signature)
130
- if existing is None or score > existing[1]:
131
- best_per_overlap[signature] = (combo_list, score)
145
+ for v in values[1:]:
146
+ next_states: dict[
147
+ frozenset[str],
148
+ tuple[int, list[BuildDatasource], tuple[int, ...], tuple[int, ...]],
149
+ ] = {}
150
+ for sig, (score, combo, combo_key, min_key) in states.items():
151
+ for idx, ds in enumerate(by_value[v]):
152
+ new_sig = sig & cols[id(ds)]
153
+ if not new_sig:
154
+ continue
155
+ new_score = score + scores[id(ds)]
156
+ new_key = combo_key + (idx,)
157
+ existing = next_states.get(new_sig)
158
+ if existing is None:
159
+ next_states[new_sig] = (
160
+ new_score,
161
+ combo + [ds],
162
+ new_key,
163
+ min_key + (idx,),
164
+ )
165
+ continue
166
+ new_min_key = min(existing[3], min_key + (idx,))
167
+ if new_score > existing[0] or (
168
+ new_score == existing[0] and new_key < existing[2]
169
+ ):
170
+ next_states[new_sig] = (
171
+ new_score,
172
+ combo + [ds],
173
+ new_key,
174
+ new_min_key,
175
+ )
176
+ else:
177
+ next_states[new_sig] = (
178
+ existing[0],
179
+ existing[1],
180
+ existing[2],
181
+ new_min_key,
182
+ )
183
+ states = next_states
184
+ if not states:
185
+ return None
132
186
 
187
+ best_per_overlap: dict[frozenset[str], tuple[list[BuildDatasource], int]] = {
188
+ sig: (state[1], state[0])
189
+ for sig, state in sorted(states.items(), key=lambda kv: kv[1][3])
190
+ }
133
191
  if not best_per_overlap:
134
192
  return None
135
193
  # Keep only maximal overlap signatures: drop a signature whose concept set
@@ -2019,7 +2019,19 @@ def _satisfy_parent_projection_contract(
2019
2019
  if not any(_contains_shape_barrier(parent) for parent in parents):
2020
2020
  return parents
2021
2021
 
2022
+ # Two different questions, two different sets. "What is available TO this
2023
+ # parent" is its own parents' outputs (`parent_output_addresses`) — that is
2024
+ # what `parent_needed` asks, and a leaf scan's empty answer is what keeps
2025
+ # leaf scans out of this projection entirely (q66). "What does a SIBLING
2026
+ # supply to the merge" is that sibling's own projection: a WINDOW sibling
2027
+ # READS `event_time` and emits only `lag(event_time)`, so crediting it with
2028
+ # its inputs made this projection strip `event_time` off the dimension
2029
+ # parent — and the consumer computing `event_time - prior_event_time` then
2030
+ # had no source for it and was dropped from the plan outright.
2022
2031
  outputs_by_parent = [parent_output_addresses(parent) for parent in parents]
2032
+ own_outputs_by_parent = [
2033
+ {output.address for output in parent.usable_outputs} for parent in parents
2034
+ ]
2023
2035
  projected: list[StrategyNode] = []
2024
2036
  for idx, parent in enumerate(parents):
2025
2037
  if _contains_shape_barrier(parent):
@@ -2027,7 +2039,7 @@ def _satisfy_parent_projection_contract(
2027
2039
  continue
2028
2040
  parent_needed = outputs_by_parent[idx] & needed
2029
2041
  other_outputs = set().union(
2030
- *(outputs for j, outputs in enumerate(outputs_by_parent) if j != idx)
2042
+ *(outputs for j, outputs in enumerate(own_outputs_by_parent) if j != idx)
2031
2043
  )
2032
2044
  fd_candidates = {
2033
2045
  addr
@@ -0,0 +1,104 @@
1
+ from trilogy.core.models.build import BuildConcept, BuildGrain, BuildWhereClause
2
+ from trilogy.core.models.build_environment import BuildEnvironment
3
+ from trilogy.core.processing.nodes import (
4
+ ConstantNode,
5
+ MergeNode,
6
+ SelectNode,
7
+ StrategyNode,
8
+ )
9
+ from trilogy.core.processing.v4_helper.functional_dependency import (
10
+ build_fd_determines,
11
+ )
12
+
13
+ from .common import outputs_with_parent_grain_keys, parent_outputs_needed
14
+
15
+
16
+ def _grain_claim_needs_group(
17
+ outputs: list[BuildConcept],
18
+ parent: StrategyNode,
19
+ environment: BuildEnvironment,
20
+ ) -> bool:
21
+ """True when this projection can only make its grain claim true by grouping.
22
+
23
+ A projection's grain is inferred from what it projects, but a plain SELECT
24
+ emits one row per PARENT row. Project `species` and `upper(species)` off a
25
+ `tree_id`-grain union and the node advertises grain `species` while emitting
26
+ one row per tree; a consumer then joins it on `species` and fans the other
27
+ side out — silently, since the SQL is valid.
28
+
29
+ Two things can make the claim true, and only the second is ours to do here:
30
+
31
+ - The claim is already true. Either the parent's grain keys survive in the
32
+ projection, or the projected grain functionally determines them (the
33
+ parent holds at most one row per it, so dropping them loses nothing).
34
+ - The projection can be widened back to the parent's grain. That is
35
+ ``outputs_with_parent_grain_keys``'s job, and it needs the parent to
36
+ actually expose those keys.
37
+
38
+ When the parent does NOT expose its own grain there is nothing to widen with,
39
+ and grouping is the only repair left. The outputs are functionally determined
40
+ by the grouped set, so the dedupe cannot drop a row — it removes exactly the
41
+ duplicates the parent's finer grain introduced.
42
+
43
+ Returns SelectNode's own default (False) when it does not fire -- passing
44
+ None instead would flip every other basic projection onto ``_resolve``'s
45
+ grain-less branch, which is a plan change for nodes this has no business
46
+ touching.
47
+ """
48
+ parent_grain = set(parent.resolve().grain.components)
49
+ if not parent_grain or parent_grain.issubset({c.address for c in outputs}):
50
+ return False
51
+ if parent_grain.issubset({c.address for c in parent.output_concepts}):
52
+ return False
53
+ claimed = BuildGrain.from_concepts(outputs).components
54
+ return not all(
55
+ component in claimed
56
+ or build_fd_determines(
57
+ environment, claimed, component, include_empty_grain=False
58
+ )
59
+ for component in parent_grain
60
+ )
61
+
62
+
63
+ def gen_basic(
64
+ outputs: list[BuildConcept],
65
+ parents: list[StrategyNode],
66
+ environment: BuildEnvironment,
67
+ conditions: BuildWhereClause | None = None,
68
+ preexisting_conditions: BuildWhereClause | None = None,
69
+ ) -> StrategyNode | None:
70
+ """Projection of derived basic expressions over already-built parents.
71
+
72
+ Zero parents → ConstantNode. One parent → SelectNode (just projection).
73
+ Multiple parents → MergeNode, which auto-joins on shared output concepts
74
+ (typically the common grain). A SelectNode here would render with no
75
+ join and emit `INVALID_REFERENCE_BUG_<...>` for the unjoined parent."""
76
+ pre = preexisting_conditions.conditional if preexisting_conditions else None
77
+ if not parents:
78
+ return ConstantNode(
79
+ input_concepts=[],
80
+ output_concepts=outputs,
81
+ environment=environment,
82
+ conditions=conditions.conditional if conditions else None,
83
+ preexisting_conditions=pre,
84
+ )
85
+ full_outputs = outputs_with_parent_grain_keys(outputs, parents)
86
+ inputs = parent_outputs_needed(full_outputs, parents, conditions)
87
+ if len(parents) == 1:
88
+ return SelectNode(
89
+ input_concepts=inputs,
90
+ output_concepts=full_outputs,
91
+ environment=environment,
92
+ parents=parents,
93
+ conditions=conditions.conditional if conditions else None,
94
+ preexisting_conditions=pre,
95
+ force_group=_grain_claim_needs_group(full_outputs, parents[0], environment),
96
+ )
97
+ return MergeNode(
98
+ input_concepts=inputs,
99
+ output_concepts=full_outputs,
100
+ environment=environment,
101
+ parents=parents,
102
+ conditions=conditions.conditional if conditions else None,
103
+ preexisting_conditions=pre,
104
+ )
@@ -1,4 +1,4 @@
1
- from collections import defaultdict
1
+ from collections import OrderedDict, defaultdict
2
2
  from dataclasses import replace
3
3
  from math import ceil
4
4
 
@@ -89,6 +89,7 @@ from trilogy.core.scope_diagnostics import (
89
89
  extract_derived_value_scopes,
90
90
  )
91
91
  from trilogy.core.statements.author import (
92
+ CallStatement,
92
93
  ChartLayer,
93
94
  ChartStatement,
94
95
  ConceptDeclarationStatement,
@@ -99,6 +100,7 @@ from trilogy.core.statements.author import (
99
100
  )
100
101
  from trilogy.core.statements.execute import (
101
102
  MaterializedDataset,
103
+ ProcessedCallStatement,
102
104
  ProcessedChartCopyStatement,
103
105
  ProcessedChartLayer,
104
106
  ProcessedChartStatement,
@@ -872,10 +874,17 @@ def _authored_reference_addresses(
872
874
  return closure
873
875
 
874
876
 
875
- # id(environment) -> (weak handle, stamp, {join key: BuildCaches}). Same
877
+ # id(environment) -> (weak handle, {stamp: {join key: BuildCaches}}). Same
876
878
  # identity idiom + mutation stamp as domain_graph._MINTED_CACHE.
877
879
  _SESSION_CACHE_STORE: dict[int, tuple] = {}
878
880
 
881
+ # Stamp generations retained per environment. More than one because refresh
882
+ # planning alternates between two long-lived states — the full environment and
883
+ # the probes' hidden-datasource window (see execution/state/isolation.py, which
884
+ # restores the stamp on exit exactly when content is restored) — and a
885
+ # single-generation store made every alternation a full baseline rebuild.
886
+ _SESSION_CACHE_GENERATIONS = 4
887
+
879
888
 
880
889
  def _evict_session_caches(key: int, _dead) -> None:
881
890
  _SESSION_CACHE_STORE.pop(key, None)
@@ -893,12 +902,16 @@ def _session_build_caches(
893
902
  datasource dict effective-write counters (content_version, not mutations —
894
903
  overlay push/pop and identical re-registrations from a re-parsed script
895
904
  must not evict the bundle), in-place datasource status flips (persist
896
- marks PUBLISHED without a dict write), and the alias map; env merges ride
897
- in the join key. If a concept overlay is somehow live at generation time,
898
- fall back to the raw mutation counter so overlay-visible state is covered.
899
- A statement with its own scoped joins gets its own bundle
900
- — address/grain-keyed cache entries are only shareable under ONE join set
901
- (the same rule that gives nested arms fresh caches)."""
905
+ marks PUBLISHED without a dict write), datasource membership, and the
906
+ alias map; env merges ride in the join key. If a concept overlay is
907
+ somehow live at generation time, fall back to the raw mutation counter so
908
+ overlay-visible state is covered. A statement with its own scoped joins
909
+ gets its own bundle — address/grain-keyed cache entries are only shareable
910
+ under ONE join set (the same rule that gives nested arms fresh caches).
911
+ A few recent stamps are retained per environment so states an executor
912
+ alternates between (full env / probe-hidden env) keep their bundles;
913
+ correctness rests on a stamp identifying exactly one content state, which
914
+ holds because counters only ever rewind on provably identical content."""
902
915
  from functools import partial
903
916
  from weakref import ref
904
917
 
@@ -911,15 +924,22 @@ def _session_build_caches(
911
924
  )
912
925
  store_key = id(environment)
913
926
  cached = _SESSION_CACHE_STORE.get(store_key)
914
- if cached is None or cached[0]() is not environment or cached[1] != stamp:
915
- bundles: dict[tuple, BuildCaches] = {}
927
+ if cached is None or cached[0]() is not environment:
928
+ generations: OrderedDict[tuple, dict[tuple, BuildCaches]] = OrderedDict()
916
929
  _SESSION_CACHE_STORE[store_key] = (
917
930
  ref(environment, partial(_evict_session_caches, store_key)),
918
- stamp,
919
- bundles,
931
+ generations,
920
932
  )
921
933
  else:
922
- bundles = cached[2]
934
+ generations = cached[1]
935
+ bundles = generations.get(stamp)
936
+ if bundles is None:
937
+ bundles = {}
938
+ generations[stamp] = bundles
939
+ while len(generations) > _SESSION_CACHE_GENERATIONS:
940
+ generations.popitem(last=False)
941
+ else:
942
+ generations.move_to_end(stamp)
923
943
  key = environment.materialize_join_key(scoped_joins)
924
944
  caches = bundles.get(key)
925
945
  if caches is None:
@@ -1266,17 +1286,45 @@ def process_copy(
1266
1286
  )
1267
1287
 
1268
1288
 
1269
- def _binding_safe_address(binding, environment: Environment) -> str:
1289
+ def process_call(
1290
+ environment: Environment,
1291
+ statement: CallStatement,
1292
+ hooks: list[BaseHook] | None = None,
1293
+ ) -> ProcessedCallStatement:
1294
+ if statement.select is None:
1295
+ return ProcessedCallStatement(target=statement.target)
1296
+ query = process_query(
1297
+ environment=environment, statement=statement.select, hooks=hooks
1298
+ )
1299
+ return ProcessedCallStatement(target=statement.target, query=query)
1300
+
1301
+
1302
+ def _binding_safe_address(
1303
+ binding, environment: Environment, output_fields: set[str]
1304
+ ) -> str:
1305
+ """The select-output field a binding names.
1306
+
1307
+ An `as` alias resolves to a projected output of that name when one exists;
1308
+ otherwise it is a display label only and the bound concept itself is the
1309
+ field. Computed expressions have no address, so they require a projected
1310
+ alias."""
1270
1311
  from trilogy.core.models.author import compute_safe_address
1271
1312
 
1272
1313
  if binding.alias is not None:
1273
1314
  namespace = environment.namespace or DEFAULT_NAMESPACE
1274
- return compute_safe_address(namespace, binding.alias)
1315
+ alias_safe = compute_safe_address(namespace, binding.alias)
1316
+ if alias_safe in output_fields:
1317
+ return alias_safe
1275
1318
  if isinstance(binding.expr, ConceptRef):
1276
1319
  return binding.expr.safe_address
1320
+ if binding.alias is None:
1321
+ raise ValueError(
1322
+ f"Chart binding for role '{binding.role}' has a computed expression"
1323
+ " without an alias"
1324
+ )
1277
1325
  raise ValueError(
1278
- f"Chart binding for role '{binding.role}' has a computed expression"
1279
- " without an alias"
1326
+ f"Chart role '{binding.role}' aliases '{binding.alias}' but no select"
1327
+ f" output has that name: {output_fields}"
1280
1328
  )
1281
1329
 
1282
1330
 
@@ -1291,14 +1339,17 @@ def _process_chart_layer(
1291
1339
  output_fields = {c.safe_address for c in layer.select.output_components}
1292
1340
 
1293
1341
  role_map: dict[str, str] = {}
1342
+ field_labels: dict[str, str] = {}
1294
1343
  for binding in layer.bindings:
1295
- safe = _binding_safe_address(binding, environment)
1344
+ safe = _binding_safe_address(binding, environment, output_fields)
1296
1345
  if safe not in output_fields:
1297
1346
  raise ValueError(
1298
1347
  f"Chart role '{binding.role}' resolves to '{safe}' which is"
1299
1348
  f" not in select output: {output_fields}"
1300
1349
  )
1301
1350
  role_map[binding.role] = safe
1351
+ if binding.alias is not None:
1352
+ field_labels[safe] = binding.alias
1302
1353
 
1303
1354
  def _single(role: str) -> str | None:
1304
1355
  return role_map.get(role)
@@ -1308,6 +1359,7 @@ def _process_chart_layer(
1308
1359
  return ProcessedChartLayer(
1309
1360
  layer_type=layer.layer_type,
1310
1361
  query=select,
1362
+ field_labels=field_labels,
1311
1363
  x_fields=[x_field] if x_field else [],
1312
1364
  y_fields=[y_field] if y_field else [],
1313
1365
  color_field=_single("color"),
@@ -240,7 +240,14 @@ class SelectStatement(HasUUID, SelectTypeMixin):
240
240
  ]
241
241
  return SelectLineage(
242
242
  selection=[
243
- environment.concepts[x.concept.address].reference
243
+ # An ephemeral parse never commits its select aliases, so a
244
+ # locally-derived output may exist only on the statement.
245
+ (
246
+ self.local_concepts[x.concept.address]
247
+ if x.concept.address not in environment.concepts
248
+ and x.concept.address in self.local_concepts
249
+ else environment.concepts[x.concept.address]
250
+ ).reference
244
251
  for x in self.selection
245
252
  ],
246
253
  order_by=self.order_by,
@@ -552,6 +559,18 @@ class CopyStatement:
552
559
  meta: Metadata | None = field(default_factory=Metadata)
553
560
 
554
561
 
562
+ @dataclass
563
+ class CallStatement:
564
+ target: str
565
+ select: SelectStatement | None = None
566
+ meta: Metadata | None = field(default_factory=Metadata)
567
+
568
+
569
+ def call_arg_name(address: str) -> str:
570
+ """The ``--flag`` name a call select output supplies (validated at parse)."""
571
+ return address.rsplit(".", 1)[-1]
572
+
573
+
555
574
  @dataclass
556
575
  class MultiSelectStatement(HasUUID, SelectTypeMixin):
557
576
  selects: list[SelectStatement]
@@ -841,6 +860,9 @@ class ChartLayer:
841
860
  layer_type: ChartType
842
861
  bindings: list[ChartLayerBinding] = field(default_factory=list)
843
862
  select: SelectStatement | None = None
863
+ # True when the author wrote `from select ...`; an implicit select is
864
+ # rebuilt from the bindings, so rendering must not echo it back.
865
+ explicit_select: bool = False
844
866
 
845
867
 
846
868
  @dataclass
@@ -865,6 +887,7 @@ STATEMENT_TYPES = (
865
887
  SelectStatement
866
888
  | RawSQLStatement
867
889
  | CopyStatement
890
+ | CallStatement
868
891
  | MultiSelectStatement
869
892
  | RowsetDerivationStatement
870
893
  | MergeStatementV2
@@ -93,6 +93,12 @@ class ProcessedRawSQLStatement:
93
93
  text: str
94
94
 
95
95
 
96
+ @dataclass
97
+ class ProcessedCallStatement:
98
+ target: str
99
+ query: ProcessedQuery | None = None
100
+
101
+
96
102
  @dataclass
97
103
  class ProcessedValidateStatement:
98
104
  scope: ValidationScope
@@ -175,6 +181,9 @@ class ProcessedShowStatement:
175
181
  class ProcessedChartLayer:
176
182
  layer_type: "ChartType"
177
183
  query: ProcessedQuery | None = None
184
+ # display names for fields whose binding carried an `as` alias, keyed by
185
+ # the field's safe address; renderers fall back to the address itself
186
+ field_labels: dict[str, str] = field(default_factory=dict)
178
187
  x_fields: list[str] = field(default_factory=list)
179
188
  y_fields: list[str] = field(default_factory=list)
180
189
  color_field: str | None = None
@@ -210,6 +219,7 @@ from trilogy.core.statements.author import ChartPlacement
210
219
 
211
220
  PROCESSED_STATEMENT_TYPES = (
212
221
  ProcessedCopyStatement
222
+ | ProcessedCallStatement
213
223
  | ProcessedQuery
214
224
  | ProcessedRawSQLStatement
215
225
  | ProcessedQueryPersist