pytrilogy 0.3.332__tar.gz → 0.3.334__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 (408) hide show
  1. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/PKG-INFO +1 -1
  2. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/__init__.py +1 -1
  3. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/ai/constants.py +6 -10
  4. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/ai/syntax_examples.py +10 -49
  5. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/models/build.py +6 -0
  6. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/models/core.py +4 -5
  7. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/models/execute.py +16 -0
  8. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/join_resolution.py +65 -4
  9. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/nodes/base_node.py +30 -3
  10. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/nodes/filter_node.py +2 -0
  11. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/nodes/select_node_v2.py +2 -0
  12. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/group_graph.py +56 -6
  13. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/strategy_builder.py +204 -136
  14. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/base.py +13 -3
  15. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/bigquery.py +46 -0
  16. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/common.py +8 -2
  17. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/duckdb.py +2 -0
  18. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/mysql.py +4 -1
  19. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/agent_info_docs/cli.py +3 -2
  20. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/Cargo.lock +1 -1
  21. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/Cargo.toml +1 -1
  22. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/explore.py +1 -2
  23. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/file.py +22 -2
  24. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/.scripts/__init__.py +0 -0
  25. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/.scripts/build_backend.py +0 -0
  26. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/.scripts/sync_version.py +0 -0
  27. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/LICENSE.md +0 -0
  28. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/README.md +0 -0
  29. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/pyproject.toml +0 -0
  30. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/ai/README.md +0 -0
  31. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/ai/__init__.py +0 -0
  32. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/ai/conversation.py +0 -0
  33. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/ai/enums.py +0 -0
  34. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/ai/execute.py +0 -0
  35. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/ai/models.py +0 -0
  36. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/ai/prompts.py +0 -0
  37. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/ai/providers/__init__.py +0 -0
  38. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/ai/providers/anthropic.py +0 -0
  39. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/ai/providers/base.py +0 -0
  40. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/ai/providers/deepseek.py +0 -0
  41. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/ai/providers/google.py +0 -0
  42. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/ai/providers/openai.py +0 -0
  43. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/ai/providers/openrouter.py +0 -0
  44. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/ai/providers/utils.py +0 -0
  45. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/authoring/README.md +0 -0
  46. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/authoring/__init__.py +0 -0
  47. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/constants.py +0 -0
  48. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/README.md +0 -0
  49. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/__init__.py +0 -0
  50. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/constants.py +0 -0
  51. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/domain_graph.py +0 -0
  52. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/domain_validation.py +0 -0
  53. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/enums.py +0 -0
  54. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/env_processor.py +0 -0
  55. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/environment_helpers.py +0 -0
  56. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/ergonomics.py +0 -0
  57. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/exceptions.py +0 -0
  58. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/fingerprint.py +0 -0
  59. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/functions.py +0 -0
  60. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/graph.py +0 -0
  61. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/graph_models.py +0 -0
  62. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/having_normalization.py +0 -0
  63. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/internal.py +0 -0
  64. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/models/__init__.py +0 -0
  65. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/models/author.py +0 -0
  66. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/models/build_environment.py +0 -0
  67. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/models/datasource.py +0 -0
  68. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/models/environment.py +0 -0
  69. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/optimization.py +0 -0
  70. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/optimizations/__init__.py +0 -0
  71. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/optimizations/base_optimization.py +0 -0
  72. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/optimizations/collapse_single_parent.py +0 -0
  73. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/optimizations/filtered_aggregate.py +0 -0
  74. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/optimizations/filtered_count_join.py +0 -0
  75. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/optimizations/full_join_lowering.py +0 -0
  76. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/optimizations/hide_unused_concept.py +0 -0
  77. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/optimizations/inline_datasource.py +0 -0
  78. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/optimizations/join_hoist.py +0 -0
  79. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/optimizations/join_upgrade.py +0 -0
  80. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/optimizations/keyless_full_join.py +0 -0
  81. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/optimizations/merge_irrelevant_group_by.py +0 -0
  82. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/optimizations/null_safe_join.py +0 -0
  83. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/optimizations/order_inner_joins.py +0 -0
  84. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  85. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/optimizations/semi_join_pushdown.py +0 -0
  86. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/optimizations/strip_redundant_not_null.py +0 -0
  87. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/optimizations/union_dim_pushdown.py +0 -0
  88. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/optimizations/utils.py +0 -0
  89. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/optimizations/value_set_join_upgrade.py +0 -0
  90. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/README.md +0 -0
  91. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/VIRTUAL_UNNEST.md +0 -0
  92. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/__init__.py +0 -0
  93. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/aggregate_rollup.py +0 -0
  94. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/concept_strategies_v4.py +0 -0
  95. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/condition_utility.py +0 -0
  96. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/constants.py +0 -0
  97. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/discovery_utility.py +0 -0
  98. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/discovery_validation.py +0 -0
  99. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/grain_utility.py +0 -0
  100. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/model_ambiguity.py +0 -0
  101. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/node_generators/__init__.py +0 -0
  102. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/node_generators/common.py +0 -0
  103. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/node_generators/presence_probe.py +0 -0
  104. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  105. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/node_generators/select_helpers/condition_routing.py +0 -0
  106. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  107. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/node_generators/select_helpers/datasource_nodes.py +0 -0
  108. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/node_generators/select_helpers/source_scoring.py +0 -0
  109. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  110. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/node_generators/select_node.py +0 -0
  111. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/nodes/README.md +0 -0
  112. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/nodes/__init__.py +0 -0
  113. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/nodes/group_node.py +0 -0
  114. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/nodes/merge_node.py +0 -0
  115. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/nodes/recursive_node.py +0 -0
  116. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/nodes/subselect_node.py +0 -0
  117. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/nodes/union_node.py +0 -0
  118. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  119. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/nodes/window_node.py +0 -0
  120. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/rowset_islanding.py +0 -0
  121. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/utility.py +0 -0
  122. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/__init__.py +0 -0
  123. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/concept_graph.py +0 -0
  124. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/condition_injection.py +0 -0
  125. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/condition_placement.py +0 -0
  126. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/constants.py +0 -0
  127. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/edges.py +0 -0
  128. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/functional_dependency.py +0 -0
  129. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/group_behaviors.py +0 -0
  130. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/group_rules.py +0 -0
  131. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/history.py +0 -0
  132. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/models.py +0 -0
  133. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/network_build.py +0 -0
  134. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/network_coalescing.py +0 -0
  135. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/network_model.py +0 -0
  136. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/network_obligations.py +0 -0
  137. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/network_search.py +0 -0
  138. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/network_topology.py +0 -0
  139. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/projection.py +0 -0
  140. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/source_planning.py +0 -0
  141. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_helper/staged_where.py +0 -0
  142. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_node_generators/__init__.py +0 -0
  143. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_node_generators/aggregate.py +0 -0
  144. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_node_generators/basic.py +0 -0
  145. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_node_generators/common.py +0 -0
  146. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_node_generators/condition_sources.py +0 -0
  147. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_node_generators/constant.py +0 -0
  148. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_node_generators/dispatch.py +0 -0
  149. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_node_generators/filter.py +0 -0
  150. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_node_generators/group_to.py +0 -0
  151. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_node_generators/multiselect.py +0 -0
  152. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_node_generators/nested_select.py +0 -0
  153. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_node_generators/recursive.py +0 -0
  154. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_node_generators/root.py +0 -0
  155. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_node_generators/rowset.py +0 -0
  156. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_node_generators/subselect.py +0 -0
  157. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_node_generators/union.py +0 -0
  158. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_node_generators/union_select.py +0 -0
  159. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_node_generators/unnest.py +0 -0
  160. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/processing/v4_node_generators/window.py +0 -0
  161. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/query_processor.py +0 -0
  162. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/scope_diagnostics.py +0 -0
  163. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/statements/README.md +0 -0
  164. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/statements/__init__.py +0 -0
  165. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/statements/author.py +0 -0
  166. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/statements/build.py +0 -0
  167. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/statements/common.py +0 -0
  168. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/statements/execute.py +0 -0
  169. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/table_processor.py +0 -0
  170. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/utility.py +0 -0
  171. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/validation/README.md +0 -0
  172. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/validation/__init__.py +0 -0
  173. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/validation/common.py +0 -0
  174. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/validation/concept.py +0 -0
  175. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/validation/datasource.py +0 -0
  176. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/validation/environment.py +0 -0
  177. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/validation/fix.py +0 -0
  178. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/validation/rows.py +0 -0
  179. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/core/where_scope_normalization.py +0 -0
  180. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/__init__.py +0 -0
  181. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/bigquery_engine.py +0 -0
  182. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/bigquery_persist.py +0 -0
  183. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/bigquery_staging.py +0 -0
  184. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/cancel.py +0 -0
  185. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/clickhouse.py +0 -0
  186. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/clickhouse_chdb.py +0 -0
  187. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/config.py +0 -0
  188. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/dataframe.py +0 -0
  189. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/duckdb_uv.py +0 -0
  190. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/enums.py +0 -0
  191. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/metadata.py +0 -0
  192. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/mock.py +0 -0
  193. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/postgres.py +0 -0
  194. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/presto.py +0 -0
  195. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/python_source.py +0 -0
  196. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/results.py +0 -0
  197. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/snowflake.py +0 -0
  198. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/source_pushdown.py +0 -0
  199. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/sql_server.py +0 -0
  200. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/dialect/sqlite.py +0 -0
  201. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/engine.py +0 -0
  202. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/execution/__init__.py +0 -0
  203. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/execution/config.py +0 -0
  204. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/execution/envs.py +0 -0
  205. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/execution/model_fingerprint.py +0 -0
  206. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/execution/report.py +0 -0
  207. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/execution/state/AGENTS.md +0 -0
  208. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/execution/state/CLAUDE.md +0 -0
  209. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/execution/state/__init__.py +0 -0
  210. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/execution/state/cache.py +0 -0
  211. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/execution/state/exceptions.py +0 -0
  212. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/execution/state/isolation.py +0 -0
  213. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/execution/state/partitions.py +0 -0
  214. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/execution/state/persistence.py +0 -0
  215. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/execution/state/phases.py +0 -0
  216. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/execution/state/snapshot.py +0 -0
  217. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/execution/state/state_store.py +0 -0
  218. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/execution/state/watermarks.py +0 -0
  219. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/executor.py +0 -0
  220. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/hooks/__init__.py +0 -0
  221. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/hooks/base_hook.py +0 -0
  222. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/hooks/graph_hook.py +0 -0
  223. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/hooks/query_debugger.py +0 -0
  224. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/io/__init__.py +0 -0
  225. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/io/adapters.py +0 -0
  226. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/io/arrow.py +0 -0
  227. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/io/click_support.py +0 -0
  228. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/io/contract.py +0 -0
  229. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/io/describe.py +0 -0
  230. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/io/errors.py +0 -0
  231. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/io/runner.py +0 -0
  232. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/io/sinks.py +0 -0
  233. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/metadata/__init__.py +0 -0
  234. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parser.py +0 -0
  235. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/README.md +0 -0
  236. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/__init__.py +0 -0
  237. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/common.py +0 -0
  238. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/config.py +0 -0
  239. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/exceptions.py +0 -0
  240. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/helpers.py +0 -0
  241. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/parse_engine_v2.py +0 -0
  242. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/pretty.py +0 -0
  243. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/render.py +0 -0
  244. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/trilogy.lark +0 -0
  245. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/README.md +0 -0
  246. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/__init__.py +0 -0
  247. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/concept_factory.py +0 -0
  248. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/concept_syntax.py +0 -0
  249. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/errors.py +0 -0
  250. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/function_syntax.py +0 -0
  251. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/hydration.py +0 -0
  252. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/import_service.py +0 -0
  253. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/lark_backend.py +0 -0
  254. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/model.py +0 -0
  255. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/pest_backend.py +0 -0
  256. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rowset_semantics.py +0 -0
  257. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/__init__.py +0 -0
  258. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/chart_rules.py +0 -0
  259. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/concept_rules.py +0 -0
  260. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/conditional_rules.py +0 -0
  261. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/datasource_rules.py +0 -0
  262. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/expression_rules.py +0 -0
  263. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/function_definition_rules.py +0 -0
  264. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/function_rules.py +0 -0
  265. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/import_rules.py +0 -0
  266. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/merge_rules.py +0 -0
  267. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/multiselect_rules.py +0 -0
  268. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/operational_rules.py +0 -0
  269. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/order_rules.py +0 -0
  270. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/persist_rules.py +0 -0
  271. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/rawsql_rules.py +0 -0
  272. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/rowset_rules.py +0 -0
  273. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/select_rules.py +0 -0
  274. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/select_statement_rules.py +0 -0
  275. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/statement_rules.py +0 -0
  276. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/subselect_rules.py +0 -0
  277. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/token_rules.py +0 -0
  278. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/tvf_rules.py +0 -0
  279. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules/type_rules.py +0 -0
  280. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/rules_context.py +0 -0
  281. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/select_finalize.py +0 -0
  282. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/semantic_scope.py +0 -0
  283. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/semantic_state.py +0 -0
  284. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/statement_planner.py +0 -0
  285. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/statement_plans.py +0 -0
  286. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/symbols.py +0 -0
  287. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/parsing/v2/syntax.py +0 -0
  288. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/py.typed +0 -0
  289. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/render.py +0 -0
  290. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/rendering/__init__.py +0 -0
  291. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/rendering/altair_renderer.py +0 -0
  292. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/rendering/base.py +0 -0
  293. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/rendering/chart_theme.py +0 -0
  294. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/rendering/rich_types.py +0 -0
  295. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/rendering/terminal_renderer.py +0 -0
  296. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/rendering/theme.py +0 -0
  297. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/report/__init__.py +0 -0
  298. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/report/backends/__init__.py +0 -0
  299. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/report/backends/base.py +0 -0
  300. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/report/backends/html.py +0 -0
  301. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/report/backends/png.py +0 -0
  302. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/report/charts.py +0 -0
  303. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/report/document.py +0 -0
  304. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/report/runner.py +0 -0
  305. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/report/tables.py +0 -0
  306. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/AGENTS.md +0 -0
  307. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/CLAUDE.md +0 -0
  308. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/README.md +0 -0
  309. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/__init__.py +0 -0
  310. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/agent.py +0 -0
  311. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/agent_info.py +0 -0
  312. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/agent_info_docs/__init__.py +0 -0
  313. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/agent_info_docs/content.py +0 -0
  314. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/agent_info_docs/directory.py +0 -0
  315. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/agent_sessions.py +0 -0
  316. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/agent_sql_tools.py +0 -0
  317. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/agent_tools.py +0 -0
  318. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/click_utils.py +0 -0
  319. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/cloud.py +0 -0
  320. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/cloud_models.py +0 -0
  321. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/common.py +0 -0
  322. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/database.py +0 -0
  323. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/README.md +0 -0
  324. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/build.sh +0 -0
  325. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/examples/basic_usage.rs +0 -0
  326. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/src/directory_resolver.rs +0 -0
  327. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/src/graph.rs +0 -0
  328. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/src/lib.rs +0 -0
  329. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/src/main.rs +0 -0
  330. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/src/network_search.rs +0 -0
  331. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/src/parser.rs +0 -0
  332. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/src/python_bindings.rs +0 -0
  333. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/src/resolver.rs +0 -0
  334. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/src/trilogy.pest +0 -0
  335. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/src/trilogy_parser.rs +0 -0
  336. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/tests/base.preql +0 -0
  337. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/tests/cli_integration.rs +0 -0
  338. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/tests/customer.preql +0 -0
  339. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/tests/main.preql +0 -0
  340. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/tests/orders.preql +0 -0
  341. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/tests/public_api.rs +0 -0
  342. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/tests/test_data/base.preql +0 -0
  343. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency/tests/test_data/consumer.preql +0 -0
  344. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/dependency.py +0 -0
  345. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/display.py +0 -0
  346. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/display_core.py +0 -0
  347. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/display_execution.py +0 -0
  348. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/display_ingest.py +0 -0
  349. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/display_init.py +0 -0
  350. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/display_models.py +0 -0
  351. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/display_parallel.py +0 -0
  352. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/display_refresh.py +0 -0
  353. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/display_validation.py +0 -0
  354. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/env_commands.py +0 -0
  355. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/environment.py +0 -0
  356. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/explore_seen.py +0 -0
  357. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/file_helpers/__init__.py +0 -0
  358. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/file_helpers/backends.py +0 -0
  359. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/file_helpers/preql_description.py +0 -0
  360. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/file_helpers/preql_validation.py +0 -0
  361. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/fmt.py +0 -0
  362. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/ingest.py +0 -0
  363. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/ingest_helpers/__init__.py +0 -0
  364. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/ingest_helpers/fk_inference.py +0 -0
  365. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/ingest_helpers/foreign_keys.py +0 -0
  366. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/ingest_helpers/formatting.py +0 -0
  367. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/ingest_helpers/introspection.py +0 -0
  368. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/ingest_helpers/typing.py +0 -0
  369. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/init.py +0 -0
  370. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/parallel_execution.py +0 -0
  371. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/plan.py +0 -0
  372. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/project_config.py +0 -0
  373. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/public.py +0 -0
  374. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/refresh.py +0 -0
  375. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/render.py +0 -0
  376. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/run.py +0 -0
  377. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/serve.py +0 -0
  378. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/serve_helpers/__init__.py +0 -0
  379. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/serve_helpers/connection_spec.py +0 -0
  380. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/serve_helpers/file_discovery.py +0 -0
  381. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/serve_helpers/index_generation.py +0 -0
  382. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/serve_helpers/jobs.py +0 -0
  383. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/serve_helpers/models.py +0 -0
  384. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/serve_helpers/state_cache.py +0 -0
  385. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/serve_helpers/state_computation.py +0 -0
  386. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/serve_helpers/studio_bundle.py +0 -0
  387. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/single_execution.py +0 -0
  388. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/source.py +0 -0
  389. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/source_identity.py +0 -0
  390. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/state.py +0 -0
  391. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/testing.py +0 -0
  392. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/trilogy.py +0 -0
  393. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/scripts/validate_agent.py +0 -0
  394. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/staging.py +0 -0
  395. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/std/__init__.py +0 -0
  396. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/std/chart.preql +0 -0
  397. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/std/color.preql +0 -0
  398. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/std/currency.preql +0 -0
  399. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/std/date.preql +0 -0
  400. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/std/display.preql +0 -0
  401. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/std/geography.preql +0 -0
  402. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/std/metric.preql +0 -0
  403. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/std/money.preql +0 -0
  404. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/std/net.preql +0 -0
  405. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/std/ranking.preql +0 -0
  406. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/std/report.preql +0 -0
  407. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/std/semantic.preql +0 -0
  408. {pytrilogy-0.3.332 → pytrilogy-0.3.334}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.3.332
3
+ Version: 0.3.334
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.332"
10
+ __version__ = "0.3.334"
11
11
 
12
12
  __all__ = [
13
13
  "CONFIG",
@@ -26,24 +26,20 @@ parameter NAME TYPE [default <literal>]; — a runtime value supplied via `--par
26
26
 
27
27
  ### Query-scoped join
28
28
 
29
- A typical fact already has its dimensions merged in no join needed. When blending fact models or rowset outputs, a join DECLARES how the key domains relate (allowing a path, not defining a join):
29
+ A typical fact already has its dimensions merged in, so no join is needed. Blending fact models or rowset outputs takes a scoped join, placed right after the select list, which DECLARES how the key domains relate: `subset join a = b` (a's values are contained in b's, a b; b authoritative for the key) or `union join a = b` (neither contains the other; the key is the coalesce of both sides and unmatched rows from BOTH sides are kept).
30
30
 
31
- - `subset join a = b` — a's values are contained in b's (a b); b is authoritative for the key.
32
- - `union join a = b` — neither domain contains the other; the key is the coalesce of both sides and unmatched rows from BOTH sides are kept.
31
+ Two rules decide whether the numbers are right, so apply them before reaching for the example:
33
32
 
34
- A union key-group must be entirely union (`union join a = b = c` chains one group); subset joins mix freely. Keys may be expressions (`union join a.id + 53 = b.id`), aggregates, or windows; only `=` is supported. Joins never drop nulls (NULL keys match null-safely) and are ALWAYS effectively full — no join EVER implicitly drops a row; there is NO inner join. For an intersection, add an explicit `where <optional-side attr> is not null`.
33
+ - Joins NEVER drop a row and NULL keys match null-safely; there is NO inner join. An intersection is an explicit `where <optional-side attr> is not null`.
34
+ - JOIN ON THE FULL GRAIN: `explore` prints each fact's grain as @<k1, k2>; write one clause PER key. Matching only one key of a multi-key grain causes duplication.
35
35
 
36
- JOIN ON THE FULL GRAIN. `explore` prints each fact's grain as @<k1, k2>; a composite grain needs one join clause PER key (BOTH `union join a.order_number = b.order_number` AND `union join a.item.id = b.item.id`). Matching only one key of a multi-key grain causes duplication.
37
-
38
- Joins go right after the select list. Full example: trilogy agent-info syntax example scoped-join.
36
+ Chaining, expression keys, union/subset mixing, multi-key and self-pair shapes: trilogy agent-info syntax example scoped-join.
39
37
 
40
38
  merge <a> into ~<b>; is the persistent (whole-file) equivalent of `subset join a = b`; plain `merge a into b;` declares EXACT domain equivalence. Standalone statements; prefer a scoped join unless the connection is universal.
41
39
 
42
40
  ### union / except / intersect (row set operations)
43
41
 
44
- union((armA), (armB), ...) -> (out1, out2, ...) row-stacks self-contained select arms positionally (SQL UNION ALL) into one named result, usable in a rowset (`with combined as union(...) -> (...)`). Each arm is an ordinary select and DEDUPLICATES to its own output grain before stacking so aggregate INSIDE each arm (preferred), or carry each arm's grain key through as an extra output column; stacking raw measure rows and aggregating outside silently undersums. Example: trilogy agent-info syntax example union-stack-channels.
45
-
46
- except((armA), (armB), ...) -> (...) and intersect(...) share union's arm shape but are SQL SET operators: output rows are DISTINCT, whole rows compare null-safely, and except subtracts later arms from the FIRST (arm order matters). Prefer except over multi-column `not in` for "in A but never in B". Example: trilogy agent-info syntax example except-intersect-setops.
42
+ `union(...)` row-stacks self-contained select arms positionally (SQL UNION ALL) into one named rowset (`with combined as union((armA), (armB), ...) -> (out1, out2, ...)`); `except(...)` / `intersect(...)` share that arm shape but are SQL SET operators (DISTINCT rows, null-safe whole-row compare, and except subtracts later arms from the FIRST, so arm order matters). Prefer except over a multi-column `not in` for "in A but never in B". Each arm dedups to its own output grain BEFORE stacking, so aggregate INSIDE each arm or the total silently undersums. Examples: trilogy agent-info syntax example union-stack-channels / except-intersect-setops.
47
43
 
48
44
  ## Query structure
49
45
 
@@ -94,12 +94,7 @@ order by index;
94
94
  SyntaxExample(
95
95
  name="query-structure",
96
96
  title="Full query structure - every clause and where it goes (+ rowsets)",
97
- summary=(
98
- "the clause order of a query (`where` -> `select` <cols> -> join(s) -> "
99
- "`having` -> `order by` -> `limit`) and how to define a rowset (a NAME "
100
- "then a select); joins sit right after the select list - filter a "
101
- "joined or aggregated RESULT in `having`, input rows in `where`"
102
- ),
97
+ summary="the exact clause order of a query, and how to define a rowset",
103
98
  body="""\
104
99
  # Every Trilogy query has the SAME fixed skeleton. Top-level statements come
105
100
  # first (each ends with `;`), then ONE query whose clauses appear in this exact
@@ -169,9 +164,8 @@ limit 100;
169
164
  name="filtered-aggregate",
170
165
  title="Filtered aggregate - aggregate only the rows matching a condition",
171
166
  summary=(
172
- "`sum(x ? cond)` / `count(x ? cond)` aggregate just the matching rows; "
173
- "to COUNT ROWS count the unique grain/row key, or `count(grain(k1, k2))` "
174
- "when several keys identify it; `by <grain>` pins the aggregate's grain"
167
+ "aggregating only the rows matching a condition, and counting rows "
168
+ "correctly"
175
169
  ),
176
170
  body="""\
177
171
  # `?` is a local filter - a filter that applies only to the prior expression-
@@ -207,11 +201,7 @@ order by iris.species asc;
207
201
  "Row-level filter (WHERE) vs aggregate filter (HAVING) - don't wrap a "
208
202
  "per-row threshold in max()/min()"
209
203
  ),
210
- summary=(
211
- "a plain per-ROW condition (`x.col > N`) goes in WHERE (filters the rows "
212
- "feeding every aggregate); HAVING filters an aggregate RESULT. "
213
- "`max(col) by g > N` in HAVING filters GROUPS, not rows - different meaning"
214
- ),
204
+ summary="choosing between WHERE and HAVING for a threshold condition",
215
205
  body="""\
216
206
  # A non-aggregate, per-ROW condition belongs in WHERE - it filters the rows that
217
207
  # feed every aggregate, BEFORE grouping. HAVING is ONLY for filtering on an
@@ -396,12 +386,7 @@ order by enroll.year asc nulls first;
396
386
  SyntaxExample(
397
387
  name="union-stack-channels",
398
388
  title="Stack rows from several sources/channels with the union(...) TVF",
399
- summary=(
400
- "`with combined as union((armA), (armB), (armC), ...) -> (o1, o2, ...)` "
401
- "row-STACKS self-contained inline selects positionally (SQL UNION ALL) - "
402
- "the PREFERRED way to combine channels/sources; pass TWO OR MORE arms, one "
403
- "per source; reference outputs as `combined.o1`"
404
- ),
389
+ summary="stacking several sources/channels together as rows",
405
390
  body="""\
406
391
  # `union(...)` is a relational table-valued function: it STACKS the rows of TWO OR
407
392
  # MORE self-contained `select` arms, positionally (like SQL `UNION ALL`), into one
@@ -476,14 +461,7 @@ limit 100;
476
461
  SyntaxExample(
477
462
  name="except-intersect-setops",
478
463
  title="Set difference / co-occurrence across sources with except(...) / intersect(...)",
479
- summary=(
480
- "`with only_a as except((armA), (armB)) -> (o1, o2)` keeps the distinct "
481
- "rows of the FIRST arm that appear in NO later arm (SQL EXCEPT); "
482
- "`intersect(...)` keeps rows present in EVERY arm. PREFERRED over "
483
- "multi-column `not in` for 'in A but not B' questions when you also "
484
- "want the distinct combinations themselves: whole ROWS are compared "
485
- "(any column mix) and the output is deduplicated"
486
- ),
464
+ summary="set difference or co-occurrence across sources",
487
465
  body="""\
488
466
  # `except(...)` and `intersect(...)` are relational table-valued functions with the
489
467
  # SAME shape as `union(...)`: TWO OR MORE inline self-contained select arms, matched
@@ -529,12 +507,7 @@ limit 100;
529
507
  SyntaxExample(
530
508
  name="scoped-join",
531
509
  title="Blend two models in one query with a scoped subset/union join",
532
- summary=(
533
- "`select <cols> subset|union join a.key = b.key (= other.key ...)?` (right after "
534
- "the select list) blends a second model into ONE query - the PREFERRED "
535
- "alternative to `merge`. JOIN ON THE FULL GRAIN: one clause per key in "
536
- "the shared `@<k1, k2>` grain"
537
- ),
510
+ summary="blending a second fact model or rowset into one query",
538
511
  body="""\
539
512
  # A query-scoped `join` blends a second model into ONE select without editing the
540
513
  # model files (the query-local equivalent of `merge`). A join DECLARES how the
@@ -643,11 +616,7 @@ limit 100;
643
616
  SyntaxExample(
644
617
  name="existence-anti-join",
645
618
  title="Existence / anti-join across models",
646
- summary=(
647
- "keep or exclude rows by whether a key matches another model: "
648
- "`key in other.key` (semi-join) / `not in` (anti-join); a MULTI-part "
649
- "key uses a row-wise tuple `(a, b) in (set.a, set.b)`"
650
- ),
619
+ summary=("keeping or excluding rows by whether a key matches another model"),
651
620
  body="""\
652
621
  # `in` / `not in` against another model's key is the semi-join / anti-join
653
622
  # idiom - no manual JOIN. Use it instead of `count(...) is null` hacks.
@@ -757,12 +726,7 @@ order by unique_sc.course asc;
757
726
  SyntaxExample(
758
727
  name="rollup",
759
728
  title="Subtotals + grand total in one pass with `by rollup (…)`",
760
- summary=(
761
- "the select-level `by rollup (d1, d2)` clause computes EVERY aggregate "
762
- "in the select at every level (leaf, per-d1 subtotal, grand total) in one "
763
- "pass; `grouping(d)` = 1 on a rolled-up row - use it to LABEL "
764
- "subtotal/total rows and to sort by level"
765
- ),
729
+ summary="subtotals and a grand total in one pass, and labeling the levels",
766
730
  body="""\
767
731
  # A `by rollup (d1, d2)` clause AFTER the select list (before having/order by)
768
732
  # computes the whole select at MULTIPLE grain levels in one pass - the grouping
@@ -858,10 +822,7 @@ limit 100;
858
822
  SyntaxExample(
859
823
  name="staged-membership",
860
824
  title="Stage a coarse-grain membership set, then filter a fine-grain query",
861
- summary=(
862
- "compute a membership set in a `rowset` (keys meeting a count/HAVING), "
863
- "then filter the main query with `<key> in <rowset>.<col>`"
864
- ),
825
+ summary=("filtering a fine-grain query by a set computed at a coarser grain"),
865
826
  body="""\
866
827
  # When a query filters on a condition computed at a COARSER grain (e.g. "items
867
828
  # sold in all 3 channels", "courses offered in more than one year"), compute that
@@ -3607,6 +3607,12 @@ class Factory:
3607
3607
  self._build_over_items(list(grouping_set))
3608
3608
  for grouping_set in grouping_sets_source
3609
3609
  ]
3610
+ if grouping != AggregateGroupingMode.STANDARD and not by and not grouping_sets:
3611
+ # A rollup/cube over zero keys has exactly one grouping set - the
3612
+ # grand total - which is what a standard grouping with no keys
3613
+ # already renders. `by rollup ()` on a grainless select (only
3614
+ # constants and aggregates projected) lands here.
3615
+ grouping = AggregateGroupingMode.STANDARD
3610
3616
  if grouping == AggregateGroupingMode.STANDARD:
3611
3617
  by = sorted(by, key=lambda x: x.address)
3612
3618
 
@@ -652,11 +652,10 @@ def constant_domain_violation(
652
652
  comparison provably never match; None when satisfiable or undecidable.
653
653
 
654
654
  Only provably-FALSE predicates are flagged. In-domain predicates — even
655
- ones matching every declared value — must parse untouched: they can carry
656
- load-bearing join null-rejection through nullable FK paths
657
- (evals/tpcds_agent/bug_q16_enum_tautology_drops_joined_null_rejection.md),
658
- and a narrower authored intent than the current domain snapshot is
659
- legitimate. Error messages must never advise removing a predicate."""
655
+ ones matching every declared value — must parse untouched: through a
656
+ nullable FK they carry load-bearing join null-rejection, and a narrower
657
+ authored intent than the current domain snapshot is legitimate. Error
658
+ messages must never advise removing a predicate."""
660
659
  while isinstance(expected, TraitDataType):
661
660
  expected = expected.type
662
661
  if isinstance(value, Enum):
@@ -1196,11 +1196,17 @@ class QueryDatasource:
1196
1196
 
1197
1197
  @property
1198
1198
  def effective_grain(self) -> BuildGrain:
1199
+ # A NULL-padded key is absence, not a row identity: an anti-join
1200
+ # (`orders.id is null`) declares the preserved side's grain but still
1201
+ # projects the rejected key, and counting that key here re-inflates the
1202
+ # grain to the pre-join side and forces a regroup that dedups nothing.
1203
+ nullable = {concept.address for concept in self.nullable_concepts}
1199
1204
  key_outputs = {
1200
1205
  concept.address
1201
1206
  for concept in self.output_concepts
1202
1207
  if concept.purpose == Purpose.KEY
1203
1208
  and concept.name != RECURSIVE_GATING_CONCEPT
1209
+ and concept.address not in nullable
1204
1210
  }
1205
1211
  return self.grain + BuildGrain(components=key_outputs)
1206
1212
 
@@ -1422,12 +1428,22 @@ class QueryDatasource:
1422
1428
  limited = f"_limited_{self.limit}"
1423
1429
  if self.ordering:
1424
1430
  limited += f"_{string_to_hash(str(self.ordering))}"
1431
+ # An UNNEST in the FROM is identity for the mirror-image reason a limit
1432
+ # is: the unnested source is a row MULTIPLE of the same shape without
1433
+ # it, so the two must never collide and merge (``__add__`` rejects the
1434
+ # mismatch rather than silently folding an unnest into a plain scan).
1435
+ unnested = ""
1436
+ if self.join_derived_concepts:
1437
+ unnested = "_unnest_" + "_".join(
1438
+ sorted(c.address.replace(".", "_") for c in self.join_derived_concepts)
1439
+ )
1425
1440
  return (
1426
1441
  "_join_".join(sorted(d.identifier for d in self.datasources))
1427
1442
  + group
1428
1443
  + (f"_at_{grain}" if grain else "_at_abstract")
1429
1444
  + (f"_filtered_by_{filters}" if filters else "")
1430
1445
  + limited
1446
+ + unnested
1431
1447
  )
1432
1448
 
1433
1449
  def get_alias(
@@ -525,16 +525,77 @@ def side_nullable(concept: BuildConcept, side: DataSource | None) -> bool:
525
525
  return bool(args & nullable_addrs)
526
526
 
527
527
 
528
+ def _side_outputs(concept: BuildConcept, side: DataSource) -> bool:
529
+ equivalent = concept.equivalent_addresses
530
+ return any(equivalent & c.equivalent_addresses for c in side.output_concepts)
531
+
532
+
533
+ def nulls_are_values(
534
+ concept: BuildConcept,
535
+ side: DataSource,
536
+ _seen: frozenset[tuple[str, int]] = frozenset(),
537
+ ) -> bool:
538
+ """Whether the NULLs this side carries for ``concept`` are VALUES (a `?`
539
+ column, a nullable derivation, a nullable input to a null-propagating
540
+ expression, a ROLLUP grouping key) rather than pure outer-join extension.
541
+
542
+ Outer-join extension means *absent*: there is no row on that side, so no
543
+ key. Pairing that against a real NULL group cross-joins the two (q30: 311
544
+ padded address rows x 20 unknown-customer groups = 6,220)."""
545
+ if concept.is_nullable:
546
+ return True
547
+ # Argument chains can be mutually recursive (q25); a repeat visit of the
548
+ # same concept on the same source contributes nothing new.
549
+ visit = (concept.address, id(side))
550
+ if visit in _seen:
551
+ return False
552
+ seen = _seen | {visit}
553
+ equivalent = concept.equivalent_addresses
554
+ if isinstance(side, BuildDatasource):
555
+ # Column-level `?` is the only value-NULL source on a physical table.
556
+ return any(
557
+ equivalent & nc.equivalent_addresses for nc in side.nullable_concepts
558
+ )
559
+ # A grouping-set NULL is padding too, but a twin-rollup partner pads the
560
+ # same key, so it stays pairable here; get_join_type handles the mismatch.
561
+ if equivalent & rollup_padded_addresses(side):
562
+ return True
563
+ carriers = [p for p in side.datasources if _side_outputs(concept, p)]
564
+ if not carriers:
565
+ # Nothing upstream to attribute the NULL to; stay conservative rather
566
+ # than call an unexplained nullability extension.
567
+ return True
568
+ if any(nulls_are_values(concept, p, seen) for p in carriers):
569
+ return True
570
+ if not propagates_argument_nulls(concept):
571
+ return False
572
+ args = {a.address for a in concept.concept_arguments}
573
+ if not args:
574
+ return False
575
+ return any(
576
+ (args & nc.equivalent_addresses) and nulls_are_values(nc, side, seen)
577
+ for nc in side.nullable_concepts
578
+ )
579
+
580
+
528
581
  def get_modifiers(
529
582
  left_concept: BuildConcept,
530
583
  right_concept: BuildConcept,
531
584
  left: DataSource | None,
532
585
  right: DataSource | None,
533
586
  ) -> list[Modifier]:
534
- """Use null-safe equality only when both exposed join keys can be NULL."""
535
- if side_nullable(left_concept, left) and side_nullable(right_concept, right):
536
- return [Modifier.NULLABLE]
537
- return []
587
+ """Use null-safe equality only when both exposed join keys can be NULL.
588
+
589
+ Asymmetric padding is the exception: when one side's NULLs are outer-join
590
+ extension (absence) and the other's are values, they name nothing in
591
+ common and null-safe equality cross-joins them. Both sides extended is the
592
+ ordinary case again: the padding shares provenance, so those rows pair."""
593
+ if not (side_nullable(left_concept, left) and side_nullable(right_concept, right)):
594
+ return []
595
+ assert left is not None and right is not None
596
+ if nulls_are_values(left_concept, left) != nulls_are_values(right_concept, right):
597
+ return []
598
+ return [Modifier.NULLABLE]
538
599
 
539
600
 
540
601
  def _collect_deep_partial_addresses(
@@ -177,6 +177,10 @@ def get_all_parent_nullable(
177
177
 
178
178
  class StrategyNode:
179
179
  source_type = SourceType.ABSTRACT
180
+ # A node that only projects or filters emits its parents' rows. Subclasses
181
+ # that set this take their parents' grain when none was passed, rather than
182
+ # deriving one from their own outputs; see `_default_grain`.
183
+ inherits_parent_grain: bool = False
180
184
 
181
185
  def __init__(
182
186
  self,
@@ -539,14 +543,37 @@ class StrategyNode:
539
543
  source_map[concept.address] = {ps}
540
544
  break
541
545
 
546
+ def _default_grain(
547
+ self, parent_sources: list[QueryDatasource | BuildDatasource]
548
+ ) -> BuildGrain:
549
+ """The grain to declare when the planner passed none.
550
+
551
+ Deriving it from ``output_concepts`` states the grain the projection
552
+ *selects*, which is only the row grain if something deduped to it. For a
553
+ row-preserving node nothing did, so it reports its parents' rows: a
554
+ narrowing projection that claims the narrow grain reads as already
555
+ deduped, and consumers skip the GROUP BY that would make it true.
556
+ """
557
+ if self.inherits_parent_grain and parent_sources and not self.force_group:
558
+ # An existence feeder is read through a subselect: it rejects rows,
559
+ # it never supplies them, so its grain is not part of ours.
560
+ existence = {concept.address for concept in self.existence_concepts}
561
+ inherited = BuildGrain()
562
+ for source in parent_sources:
563
+ supplied = {concept.address for concept in source.output_concepts}
564
+ if existence and supplied <= existence:
565
+ continue
566
+ inherited += source.grain
567
+ if inherited.components:
568
+ return inherited
569
+ return BuildGrain.from_concepts(self.output_concepts)
570
+
542
571
  def _resolve(self) -> QueryDatasource:
543
572
  parent_sources: list[QueryDatasource | BuildDatasource] = [
544
573
  p.resolve() for p in self.parents
545
574
  ]
546
575
 
547
- grain = (
548
- self.grain if self.grain else BuildGrain.from_concepts(self.output_concepts)
549
- )
576
+ grain = self.grain if self.grain else self._default_grain(parent_sources)
550
577
  source_map = resolve_concept_map(
551
578
  parent_sources,
552
579
  targets=self.output_concepts,
@@ -20,6 +20,8 @@ class FilterNode(StrategyNode):
20
20
 
21
21
  source_type = SourceType.FILTER
22
22
 
23
+ inherits_parent_grain = True
24
+
23
25
  def __init__(
24
26
  self,
25
27
  input_concepts: list[BuildConcept],
@@ -28,6 +28,8 @@ class SelectNode(StrategyNode):
28
28
 
29
29
  source_type = SourceType.SELECT
30
30
 
31
+ inherits_parent_grain = True
32
+
31
33
  def __init__(
32
34
  self,
33
35
  input_concepts: list[BuildConcept],
@@ -1783,16 +1783,37 @@ def _consumer_required_input_grain(
1783
1783
  # a raw scan with it is unrenderable. The parent's grain (added below) is
1784
1784
  # the joinable identity.
1785
1785
  parent_computed: set[str] = set()
1786
- for pred in group_graph.predecessors(gid):
1787
- if pred == FINAL_NODE_ID or pred not in attrs:
1788
- continue
1789
- if edge_kind(group_edges, pred, gid) == EdgeKind.EXISTENCE:
1790
- continue
1786
+ row_preds = [
1787
+ pred
1788
+ for pred in group_graph.predecessors(gid)
1789
+ if pred != FINAL_NODE_ID
1790
+ and pred in attrs
1791
+ and edge_kind(group_edges, pred, gid) != EdgeKind.EXISTENCE
1792
+ ]
1793
+ for pred in row_preds:
1791
1794
  if (
1792
1795
  attrs[pred].derivation in GROUPING_DERIVATIONS
1793
1796
  or attrs[pred].derivation == Derivation.ROWSET
1794
1797
  ):
1795
- grain |= set(attrs[pred].grain_components)
1798
+ # A pred whose row stream reaches this consumer through a GROUPING
1799
+ # descendant pred that grouped the axis away has no such axis at
1800
+ # this merge: requiring it resurrects the raw pred as a parent CTE
1801
+ # beside the descendant, and the two share no joinable key (rollup
1802
+ # over union-joined rowsets: the raw `r` rowset beside the rollup
1803
+ # aggregate, whose ROLLUP grain is `channel` only). The
1804
+ # descendant's own grain, declared below on its own iteration, is
1805
+ # the merge axis.
1806
+ pred_grain = set(attrs[pred].grain_components)
1807
+ axis_grouped_away = any(
1808
+ other != pred
1809
+ and pred in nx.ancestors(group_graph, other)
1810
+ and attrs[other].derivation in GROUPING_DERIVATIONS
1811
+ and not (pred_grain <= set(attrs[other].output_concepts))
1812
+ for other in row_preds
1813
+ )
1814
+ if axis_grouped_away:
1815
+ continue
1816
+ grain |= pred_grain
1796
1817
  return frozenset(grain - parent_computed)
1797
1818
 
1798
1819
 
@@ -2271,6 +2292,21 @@ def _compute_concept_sets(
2271
2292
  pseudonym_mates = {k: frozenset(v) for k, v in mate_accumulator.items()}
2272
2293
 
2273
2294
  io = GroupIOPlan.for_groups(group_graph)
2295
+ # Non-ROWSET members of authored statement relations that NO group hosts:
2296
+ # the axis vocabulary a fresh scan may advertise below. A member some group
2297
+ # already carries as a primary needs no re-sourcing, and advertising it
2298
+ # there only widens the scan and the merge keys with equalities the fact
2299
+ # grain already determines.
2300
+ scan_advertisable_members: list[str] = []
2301
+ if environment is not None:
2302
+ for member in sorted(_statement_scoped_relation_members(environment)):
2303
+ member_concept = environment.concepts.get(member)
2304
+ if (
2305
+ member_concept is not None
2306
+ and member_concept.derivation != Derivation.ROWSET
2307
+ and member not in all_primary_members
2308
+ ):
2309
+ scan_advertisable_members.append(member)
2274
2310
  for gid in topo:
2275
2311
  if gid == FINAL_NODE_ID:
2276
2312
  continue
@@ -2279,6 +2315,20 @@ def _compute_concept_sets(
2279
2315
  if fact.behavior is None or fact.derivation == Derivation.ROOT:
2280
2316
  for addr in list(cap):
2281
2317
  cap.update(source_grain_of.get(addr, frozenset()))
2318
+ # A fresh scan can re-source an authored scoped-join axis member
2319
+ # its columns FD-determine (`billing_customer.sk` determines the
2320
+ # unique `billing_customer.id` a `subset join cust.cid = ...id`
2321
+ # names). Advertise it so the demand rules can elect the axis for
2322
+ # the merge; without it the address intersection fails and the
2323
+ # FINAL merge loses the authored join key (all_sales pivot).
2324
+ # Rowset handles are excluded: a scan that absorbs one would drop
2325
+ # the rowset's internal filter.
2326
+ if environment is not None:
2327
+ for member in scan_advertisable_members:
2328
+ if member not in cap and build_fd_determines(
2329
+ environment, cap, member, include_empty_grain=False
2330
+ ):
2331
+ cap.add(member)
2282
2332
  io.capability[gid] = cap
2283
2333
  continue
2284
2334
  # A grouping group's grain component that is a STATEMENT-scoped join axis