data-flow-control 0.1.8__tar.gz → 0.2.1__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 (205) hide show
  1. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/Cargo.lock +3 -3
  2. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/Cargo.toml +1 -1
  3. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/PKG-INFO +1 -1
  4. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/benches/rewrite_perf.rs +31 -4
  5. data_flow_control-0.2.1/passant-core/src/catalog/dimension.rs +342 -0
  6. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/catalog.rs +8 -14
  7. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/lib.rs +4 -0
  8. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/partial_push.rs +10 -0
  9. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/planner.rs +112 -20
  10. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy.rs +47 -3
  11. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy_index.rs +1 -1
  12. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy_parser.rs +7 -4
  13. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy_pgn.pest +1 -1
  14. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy_store/branch.rs +4 -2
  15. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy_store/compiled.rs +5 -0
  16. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy_store/indexes.rs +42 -0
  17. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy_store/mod.rs +110 -47
  18. data_flow_control-0.2.1/passant-core/src/resolution_placement.rs +67 -0
  19. data_flow_control-0.2.1/passant-core/src/resolution_registry.rs +117 -0
  20. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewrite_stats.rs +4 -0
  21. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewrite_strategy.rs +38 -8
  22. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/aggregates.rs +1 -1
  23. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/columns.rs +148 -0
  24. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/helpers.rs +5 -1
  25. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/insert_output_policy.rs +1 -11
  26. data_flow_control-0.2.1/passant-core/src/rewriter/joint_lineage/terminal_distinct.rs +284 -0
  27. data_flow_control-0.2.1/passant-core/src/rewriter/joint_lineage/terminal_except.rs +187 -0
  28. data_flow_control-0.2.1/passant-core/src/rewriter/joint_lineage/terminal_intersect.rs +148 -0
  29. data_flow_control-0.2.1/passant-core/src/rewriter/joint_lineage.rs +1968 -0
  30. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/limit.rs +29 -6
  31. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/mod.rs +108 -18
  32. data_flow_control-0.2.1/passant-core/src/rewriter/output_scope.rs +482 -0
  33. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/plan/actions.rs +39 -7
  34. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/plan/mod.rs +30 -5
  35. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/plan/scope_plan.rs +21 -2
  36. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/policy_expr.rs +5 -0
  37. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/resolution.rs +589 -1
  38. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/select.rs +113 -9
  39. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/types.rs +85 -1
  40. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/write_path.rs +35 -7
  41. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/source_set_index.rs +2 -2
  42. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/source_sets/branch.rs +1 -0
  43. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/source_sets/mod.rs +33 -15
  44. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/source_sets/rewrites.rs +45 -106
  45. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/source_sets/split.rs +6 -0
  46. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/sql/builders.rs +6 -2
  47. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/common/duckdb.rs +40 -1
  48. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/common/mod.rs +78 -3
  49. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/conformance.rs +47 -12
  50. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/partial_push.rs +13 -2
  51. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/pgn.rs +17 -4
  52. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/pgn_unique.rs +15 -2
  53. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/policy_storage.rs +15 -2
  54. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/rewrite_api_parity.rs +19 -3
  55. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/semiring.rs +27 -2
  56. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/source_sets.rs +59 -16
  57. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/symmetric_self_join.rs +12 -2
  58. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/threshold.rs +11 -3
  59. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/unique_not_unique.rs +19 -3
  60. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/dialect.rs +13 -3
  61. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/execution/insert.rs +2 -0
  62. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/execution/kill.rs +1 -0
  63. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/execution/oracle.rs +11 -11
  64. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/explain_strategy.rs +29 -5
  65. data_flow_control-0.2.1/passant-core/tests/gaps/column_propagation.rs +96 -0
  66. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/gaps/dimensions.rs +10 -1
  67. data_flow_control-0.2.1/passant-core/tests/gaps/exists_join.rs +79 -0
  68. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/gaps/having_where.rs +13 -1
  69. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/gaps/identifiers.rs +13 -2
  70. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/gaps/insert_shapes.rs +32 -11
  71. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/gaps/multi_source.rs +25 -7
  72. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/gaps/query_shapes.rs +20 -4
  73. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/gaps/scalar_subquery.rs +10 -1
  74. data_flow_control-0.2.1/passant-core/tests/indexed_candidate_differential.rs +156 -0
  75. data_flow_control-0.2.1/passant-core/tests/kill_null_guard_regression.rs +64 -0
  76. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/paper_examples.rs +30 -17
  77. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/planner.rs +156 -79
  78. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/policy_store_memory.rs +52 -11
  79. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/rewrite/derived_policy.rs +39 -18
  80. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/rewrite/in_semijoin.rs +23 -7
  81. data_flow_control-0.2.1/passant-core/tests/rewrite/insert.rs +188 -0
  82. data_flow_control-0.2.1/passant-core/tests/rewrite/joins.rs +508 -0
  83. data_flow_control-0.2.1/passant-core/tests/rewrite/main.rs +37 -0
  84. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/rewrite/meal_deduction.rs +3 -3
  85. data_flow_control-0.2.1/passant-core/tests/rewrite/output.rs +112 -0
  86. data_flow_control-0.2.1/passant-core/tests/rewrite/recursive.rs +253 -0
  87. data_flow_control-0.2.1/passant-core/tests/rewrite/scan.rs +501 -0
  88. data_flow_control-0.2.1/passant-core/tests/rewrite/update.rs +122 -0
  89. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/rewrite_candidate_regression.rs +101 -35
  90. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/rewrite_fast_paths.rs +12 -2
  91. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/rewrite_large_registry.rs +47 -15
  92. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/rewrite_stats_timings.rs +8 -2
  93. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/scan_ready_expr.rs +18 -6
  94. data_flow_control-0.2.1/passant-core/tests/source_set_split_no_parse.rs +73 -0
  95. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/ui_resolution.rs +54 -20
  96. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-py/src/bindings/planner.rs +43 -27
  97. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/pyproject.toml +1 -1
  98. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/__init__.py +7 -4
  99. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/_rust.py +4 -1
  100. data_flow_control-0.2.1/python/data_flow_control/adapters/_retry.py +58 -0
  101. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/adapters/base.py +3 -0
  102. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/adapters/datafusion.py +24 -2
  103. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/adapters/duckdb.py +58 -4
  104. data_flow_control-0.2.1/python/data_flow_control/adapters/postgres.py +115 -0
  105. data_flow_control-0.2.1/python/data_flow_control/adapters/sqlite.py +142 -0
  106. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/connection.py +43 -3
  107. data_flow_control-0.2.1/python/data_flow_control/errors.py +48 -0
  108. data_flow_control-0.2.1/python/data_flow_control/langchain/__init__.py +24 -0
  109. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/langchain/database.py +11 -4
  110. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/planner.py +16 -0
  111. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/policy.py +15 -0
  112. data_flow_control-0.2.1/python/data_flow_control/tools/__init__.py +8 -0
  113. data_flow_control-0.2.1/python/data_flow_control/tools/retry.py +461 -0
  114. data_flow_control-0.1.8/passant-core/tests/gaps/column_propagation.rs +0 -83
  115. data_flow_control-0.1.8/passant-core/tests/gaps/exists_join.rs +0 -64
  116. data_flow_control-0.1.8/passant-core/tests/indexed_candidate_differential.rs +0 -108
  117. data_flow_control-0.1.8/passant-core/tests/rewrite/insert.rs +0 -159
  118. data_flow_control-0.1.8/passant-core/tests/rewrite/joins.rs +0 -414
  119. data_flow_control-0.1.8/passant-core/tests/rewrite/main.rs +0 -21
  120. data_flow_control-0.1.8/passant-core/tests/rewrite/output.rs +0 -85
  121. data_flow_control-0.1.8/passant-core/tests/rewrite/recursive.rs +0 -213
  122. data_flow_control-0.1.8/passant-core/tests/rewrite/scan.rs +0 -432
  123. data_flow_control-0.1.8/passant-core/tests/rewrite/update.rs +0 -102
  124. data_flow_control-0.1.8/passant-core/tests/source_set_split_no_parse.rs +0 -60
  125. data_flow_control-0.1.8/python/data_flow_control/adapters/postgres.py +0 -67
  126. data_flow_control-0.1.8/python/data_flow_control/adapters/sqlite.py +0 -73
  127. data_flow_control-0.1.8/python/data_flow_control/langchain/__init__.py +0 -15
  128. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/LICENSE +0 -0
  129. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/Cargo.toml +0 -0
  130. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/aggregate_registry.rs +0 -0
  131. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/diagnostics.rs +0 -0
  132. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/explain.rs +0 -0
  133. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/full_push.rs +0 -0
  134. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/identifiers.rs +0 -0
  135. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/intern.rs +0 -0
  136. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/ir.rs +0 -0
  137. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/optimizer.rs +0 -0
  138. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/parser.rs +0 -0
  139. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy_compile.rs +0 -0
  140. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy_store/delete.rs +0 -0
  141. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy_store/memory.rs +0 -0
  142. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/query_analysis.rs +0 -0
  143. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/constraint_preprocess.rs +0 -0
  144. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/derived_policy.rs +0 -0
  145. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/dimensions.rs +0 -0
  146. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/exists.rs +0 -0
  147. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/expr.rs +0 -0
  148. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/plan/applicability.rs +0 -0
  149. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/plan/dominance.rs +0 -0
  150. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/plan/write_plan.rs +0 -0
  151. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/projection.rs +0 -0
  152. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/scope.rs +0 -0
  153. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/semiring.rs +0 -0
  154. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/source_sets/analysis.rs +0 -0
  155. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/source_sets/annotations.rs +0 -0
  156. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/sql/ast_stats.rs +0 -0
  157. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/sql/columns.rs +0 -0
  158. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/sql/dialect.rs +0 -0
  159. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/sql/expr.rs +0 -0
  160. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/sql/expr_key.rs +0 -0
  161. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/sql/mod.rs +0 -0
  162. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/sql/parse.rs +0 -0
  163. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/sql/render.rs +0 -0
  164. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/statement_tables.rs +0 -0
  165. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/threshold.rs +0 -0
  166. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/main.rs +0 -0
  167. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/execution/main.rs +0 -0
  168. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/execution/remove.rs +0 -0
  169. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/execution/update.rs +0 -0
  170. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/expected/duckdb/remove_scan.sql +0 -0
  171. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/expected/postgres/remove_scan.sql +0 -0
  172. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/expected/sqlite/remove_scan.sql +0 -0
  173. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/gaps/main.rs +0 -0
  174. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/gaps/operators.rs +0 -0
  175. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/insert_values_body.rs +0 -0
  176. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/parser.rs +0 -0
  177. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/policy.rs +0 -0
  178. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/policy_dialect.rs +0 -0
  179. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/sql_builders.rs +0 -0
  180. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/sql_columns.rs +0 -0
  181. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-py/Cargo.toml +0 -0
  182. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-py/src/bindings/catalog.rs +0 -0
  183. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-py/src/bindings/errors.rs +0 -0
  184. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-py/src/bindings/mod.rs +0 -0
  185. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-py/src/bindings/policy.rs +0 -0
  186. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-py/src/bindings/stats.rs +0 -0
  187. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-py/src/lib.rs +0 -0
  188. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/adapters/__init__.py +0 -0
  189. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/adapters/clickhouse.py +0 -0
  190. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/adapters/kill.py +0 -0
  191. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/adapters/pg_catalog.py +0 -0
  192. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/adapters/registry.py +0 -0
  193. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/adapters/umbra.py +0 -0
  194. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/aggregate_introspection.py +0 -0
  195. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/catalog.py +0 -0
  196. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/dialect.py +0 -0
  197. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/langchain/agent.py +0 -0
  198. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/langchain/call_tool_with_dataflow.py +0 -0
  199. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/langchain/middleware.py +0 -0
  200. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/langchain/schema.py +0 -0
  201. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/langchain/sql_validation.py +0 -0
  202. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/langchain/tool_spec.py +0 -0
  203. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/langchain/wrapper.py +0 -0
  204. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/options.py +0 -0
  205. {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/ui.py +0 -0
@@ -1593,7 +1593,7 @@ dependencies = [
1593
1593
 
1594
1594
  [[package]]
1595
1595
  name = "passant-cli"
1596
- version = "0.1.8"
1596
+ version = "0.2.1"
1597
1597
  dependencies = [
1598
1598
  "anyhow",
1599
1599
  "assert_cmd",
@@ -1605,7 +1605,7 @@ dependencies = [
1605
1605
 
1606
1606
  [[package]]
1607
1607
  name = "passant-core"
1608
- version = "0.1.8"
1608
+ version = "0.2.1"
1609
1609
  dependencies = [
1610
1610
  "anyhow",
1611
1611
  "criterion",
@@ -1623,7 +1623,7 @@ dependencies = [
1623
1623
 
1624
1624
  [[package]]
1625
1625
  name = "passant-py"
1626
- version = "0.1.8"
1626
+ version = "0.2.1"
1627
1627
  dependencies = [
1628
1628
  "passant-core",
1629
1629
  "pyo3",
@@ -3,7 +3,7 @@ members = ["passant-core", "passant-py"]
3
3
  resolver = "2"
4
4
 
5
5
  [workspace.package]
6
- version = "0.1.8"
6
+ version = "0.2.1"
7
7
  edition = "2024"
8
8
  license = "MIT"
9
9
  authors = ["Charlie Summers"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: data-flow-control
3
- Version: 0.1.8
3
+ Version: 0.2.1
4
4
  Requires-Dist: duckdb>=1.3.0
5
5
  Requires-Dist: numpy>=1.24.4
6
6
  Requires-Dist: sqlglot==28.3.0
@@ -97,10 +97,31 @@ fn cte_chain_query() -> &'static str {
97
97
  SELECT id, amount FROM c"
98
98
  }
99
99
 
100
+ // Explicit synthetic benchmark schema. Slot count is a benchmark dimension;
101
+ // table/column declarations are independent of policy predicates.
102
+ fn benchmark_catalog(slot_count: usize) -> passant_core::TableCatalog {
103
+ let mut catalog = passant_core::TableCatalog::new();
104
+ for table in ["orders", "customers", "foo", "bar", "hot_source"] {
105
+ catalog.register_table(table, vec!["id".into(), "amount".into()]);
106
+ }
107
+ for index in 0..slot_count {
108
+ for table in [format!("other_{index:06}"), format!("sink_{index:04}")] {
109
+ catalog.register_table(table, vec!["id".into(), "amount".into()]);
110
+ }
111
+ }
112
+ catalog
113
+ }
114
+
100
115
  fn register_policies(rewriter: &mut PassantRewriter, policies: &[PolicyIr]) {
116
+ // This setup is outside rewrite timing loops. Registration-only benchmarks
117
+ // explicitly include schema setup and validation, unlike historical results.
118
+ *rewriter.catalog_mut() = benchmark_catalog(policies.len());
101
119
  for policy in policies {
102
- rewriter.register_policy(policy.clone());
120
+ rewriter
121
+ .register_policy(policy.clone())
122
+ .expect("benchmark policy should match declared fixture schema");
103
123
  }
124
+ assert_eq!(rewriter.policies().len(), policies.len());
104
125
  }
105
126
 
106
127
  fn bench_rewrite_fixed_applicable(c: &mut Criterion) {
@@ -205,7 +226,7 @@ fn bench_join_with_unrelated_policies(c: &mut Criterion) {
205
226
  }
206
227
 
207
228
  fn bench_policy_registration(c: &mut Criterion) {
208
- let mut group = c.benchmark_group("policy_registration");
229
+ let mut group = c.benchmark_group("policy_registration_with_catalog_setup");
209
230
  for policy_count in [1_000_usize, 10_000, 100_000] {
210
231
  let policies = generate_policies("orders", policy_count.saturating_sub(1));
211
232
  group.bench_with_input(
@@ -355,17 +376,23 @@ fn bench_rewrite_100k_ui_scan(c: &mut Criterion) {
355
376
 
356
377
  fn bench_policy_registration_compile_only(c: &mut Criterion) {
357
378
  use passant_core::policy_store::PolicyStore;
358
- let mut group = c.benchmark_group("policy_registration_compile_only");
379
+ let mut group = c.benchmark_group("policy_registration_validated_store");
359
380
  for policy_count in [1_000_usize, 10_000] {
360
381
  let policies = generate_policies("orders", policy_count.saturating_sub(1));
361
382
  group.bench_with_input(
362
383
  BenchmarkId::from_parameter(policy_count),
363
384
  &policies,
364
385
  |bencher, policies| {
386
+ let catalog = benchmark_catalog(policies.len());
387
+ let registry = passant_core::aggregate_registry::AggregateRegistry::for_dialect(
388
+ passant_core::sql::SqlDialect::DuckDb,
389
+ );
365
390
  bencher.iter(|| {
366
391
  let mut store = PolicyStore::default();
367
392
  for policy in policies {
368
- store.register(policy.clone());
393
+ store
394
+ .register(policy.clone(), &catalog, &registry)
395
+ .expect("benchmark policy should match declared fixture schema");
369
396
  }
370
397
  black_box(store);
371
398
  });
@@ -0,0 +1,342 @@
1
+ //! Conservative, non-executing binding for DIMENSION SELECT dependencies.
2
+ use super::TableCatalog;
3
+ use crate::{
4
+ ErrorKind, RewriteError,
5
+ identifiers::{ColumnName, TableKey},
6
+ };
7
+ use sqlparser::ast::{
8
+ Expr, FunctionArg, FunctionArgExpr, FunctionArguments, JoinConstraint, JoinOperator, Query,
9
+ SelectItem, SetExpr, Statement, TableFactor, Visit, Visitor,
10
+ };
11
+ use std::{collections::HashSet, ops::ControlFlow};
12
+
13
+ #[derive(Default)]
14
+ struct Relation {
15
+ qualifiers: HashSet<TableKey>,
16
+ columns: HashSet<String>,
17
+ }
18
+
19
+ pub(super) fn bound_projection(
20
+ catalog: &TableCatalog,
21
+ sql: &str,
22
+ ) -> Result<Vec<String>, RewriteError> {
23
+ let Statement::Query(query) = crate::parser::parse_query(sql)? else {
24
+ return Err(unsupported("requires a SELECT query"));
25
+ };
26
+ bind_query(catalog, &query)
27
+ }
28
+
29
+ fn unsupported(detail: &str) -> RewriteError {
30
+ RewriteError::unsupported_statement(format!("DIMENSION catalog binding {detail}"))
31
+ }
32
+ fn unknown(kind: ErrorKind, detail: String) -> RewriteError {
33
+ RewriteError::catalog_with_context(
34
+ kind,
35
+ detail,
36
+ None,
37
+ None,
38
+ None,
39
+ Some("dimension_catalog_validation"),
40
+ )
41
+ }
42
+
43
+ fn bind_query(catalog: &TableCatalog, query: &Query) -> Result<Vec<String>, RewriteError> {
44
+ if query.with.is_some()
45
+ || !query.locks.is_empty()
46
+ || query.for_clause.is_some()
47
+ || query.settings.is_some()
48
+ {
49
+ return Err(unsupported(
50
+ "does not yet support CTEs, locking, or dialect-specific query clauses",
51
+ ));
52
+ }
53
+ let (relations, outputs) = match query.body.as_ref() {
54
+ SetExpr::Query(inner) => {
55
+ let outputs = bind_query(catalog, inner)?;
56
+ (
57
+ vec![Relation {
58
+ columns: outputs.iter().cloned().collect(),
59
+ ..Default::default()
60
+ }],
61
+ outputs,
62
+ )
63
+ }
64
+ SetExpr::Select(select) => {
65
+ if select.into.is_some()
66
+ || !select.lateral_views.is_empty()
67
+ || select.connect_by.is_some()
68
+ {
69
+ return Err(unsupported(
70
+ "does not support INTO, lateral views or hierarchical queries",
71
+ ));
72
+ }
73
+ let mut relations = Vec::new();
74
+ for from in &select.from {
75
+ relations.push(bind_table(catalog, &from.relation)?);
76
+ for join in &from.joins {
77
+ let constraint = match &join.join_operator {
78
+ JoinOperator::Inner(c)
79
+ | JoinOperator::LeftOuter(c)
80
+ | JoinOperator::RightOuter(c)
81
+ | JoinOperator::FullOuter(c) => Some(c),
82
+ JoinOperator::CrossJoin => None,
83
+ _ => {
84
+ return Err(unsupported(
85
+ "supports INNER/LEFT/RIGHT/FULL ON joins and CROSS JOIN only",
86
+ ));
87
+ }
88
+ };
89
+ if constraint
90
+ .is_some_and(|c| !matches!(c, JoinConstraint::On(_) | JoinConstraint::None))
91
+ {
92
+ return Err(unsupported("does not yet bind NATURAL or USING joins"));
93
+ }
94
+ relations.push(bind_table(catalog, &join.relation)?);
95
+ // ON can see only the accumulated left side and this right side,
96
+ // never a relation introduced by a later JOIN.
97
+ if let Some(JoinConstraint::On(expr)) = constraint {
98
+ check(expr, &relations, &HashSet::new())?;
99
+ }
100
+ }
101
+ }
102
+ // FROM dependencies and ON predicates were checked in their own scopes.
103
+ // Do not revisit ON with the final scope: later relations can both hide
104
+ // an invalid forward reference and make an earlier valid name ambiguous.
105
+ let mut final_expressions = select.as_ref().clone();
106
+ final_expressions.from.clear();
107
+ check(&final_expressions, &relations, &HashSet::new())?;
108
+ let mut outputs = Vec::new();
109
+ for projection in &select.projection {
110
+ let name = match projection {
111
+ SelectItem::ExprWithAlias { alias, .. } => alias.value.clone(),
112
+ SelectItem::UnnamedExpr(Expr::Identifier(ident)) => ident.value.clone(),
113
+ SelectItem::UnnamedExpr(Expr::CompoundIdentifier(parts)) => {
114
+ parts.last().unwrap().value.clone()
115
+ }
116
+ _ => {
117
+ return Err(unsupported(
118
+ "requires explicitly named projection expressions and does not expand wildcards",
119
+ ));
120
+ }
121
+ };
122
+ let name = ColumnName::new(name).key();
123
+ if outputs.contains(&name) {
124
+ return Err(unsupported("requires unambiguous output column names"));
125
+ }
126
+ outputs.push(name);
127
+ }
128
+ (relations, outputs)
129
+ }
130
+ _ => return Err(unsupported("does not yet bind set operations or VALUES")),
131
+ };
132
+ let output_names = outputs.iter().cloned().collect();
133
+ check(&query.order_by, &relations, &output_names)?;
134
+ check(&query.limit, &relations, &HashSet::new())?;
135
+ check(&query.limit_by, &relations, &HashSet::new())?;
136
+ check(&query.offset, &relations, &HashSet::new())?;
137
+ check(&query.fetch, &relations, &HashSet::new())?;
138
+ Ok(outputs)
139
+ }
140
+
141
+ fn bind_table(catalog: &TableCatalog, factor: &TableFactor) -> Result<Relation, RewriteError> {
142
+ let TableFactor::Table {
143
+ name,
144
+ alias,
145
+ args,
146
+ version,
147
+ ..
148
+ } = factor
149
+ else {
150
+ return Err(unsupported(
151
+ "does not yet bind derived tables, table functions, or lateral/correlated scopes",
152
+ ));
153
+ };
154
+ if args.is_some() || version.is_some() || alias.as_ref().is_some_and(|a| !a.columns.is_empty())
155
+ {
156
+ return Err(unsupported(
157
+ "does not yet bind table arguments, temporal versions, or column alias lists",
158
+ ));
159
+ }
160
+ let Some(columns) = catalog.columns(&name.to_string()) else {
161
+ return Err(unknown(
162
+ ErrorKind::UnknownTable,
163
+ format!("Dimension dependency table '{name}' does not exist in the explicit catalog"),
164
+ ));
165
+ };
166
+ let qualifiers = if let Some(alias) = alias {
167
+ HashSet::from([TableKey::new(&alias.name.value)])
168
+ } else {
169
+ HashSet::from([
170
+ TableKey::new(&name.to_string()),
171
+ TableKey::new(&name.0.last().unwrap().value),
172
+ ])
173
+ };
174
+ Ok(Relation {
175
+ qualifiers,
176
+ columns: columns.iter().map(|c| ColumnName::new(c).key()).collect(),
177
+ })
178
+ }
179
+
180
+ fn check<T: Visit>(
181
+ node: &T,
182
+ relations: &[Relation],
183
+ outputs: &HashSet<String>,
184
+ ) -> Result<(), RewriteError> {
185
+ struct Binder<'a> {
186
+ relations: &'a [Relation],
187
+ outputs: &'a HashSet<String>,
188
+ }
189
+ impl Binder<'_> {
190
+ fn column(&self, qualifier: Option<TableKey>, column: &str) -> Result<(), RewriteError> {
191
+ let key = ColumnName::new(column).key();
192
+ if qualifier.is_none() && self.outputs.contains(&key) {
193
+ return Ok(());
194
+ }
195
+ let matching: Vec<_> = self
196
+ .relations
197
+ .iter()
198
+ .filter(|r| qualifier.as_ref().is_none_or(|q| r.qualifiers.contains(q)))
199
+ .collect();
200
+ let count = matching.iter().filter(|r| r.columns.contains(&key)).count();
201
+ if count == 1 {
202
+ return Ok(());
203
+ }
204
+ Err(unknown(
205
+ ErrorKind::UnknownColumn,
206
+ format!(
207
+ "Dimension dependency column '{}{}' is {}",
208
+ qualifier
209
+ .map(|q| format!("{}.", q.as_str()))
210
+ .unwrap_or_default(),
211
+ column,
212
+ if count == 0 {
213
+ "not bound to an explicit catalog column"
214
+ } else {
215
+ "ambiguous across input relations"
216
+ }
217
+ ),
218
+ ))
219
+ }
220
+ fn qualifier(&self, name: &sqlparser::ast::ObjectName) -> Result<(), RewriteError> {
221
+ let key = TableKey::new(&name.to_string());
222
+ if self
223
+ .relations
224
+ .iter()
225
+ .filter(|r| r.qualifiers.contains(&key))
226
+ .count()
227
+ == 1
228
+ {
229
+ Ok(())
230
+ } else {
231
+ Err(unknown(
232
+ ErrorKind::UnknownTable,
233
+ format!("Dimension wildcard qualifier '{name}' is not bound unambiguously"),
234
+ ))
235
+ }
236
+ }
237
+ }
238
+ impl Visitor for Binder<'_> {
239
+ type Break = RewriteError;
240
+ fn pre_visit_query(&mut self, _query: &Query) -> ControlFlow<Self::Break> {
241
+ ControlFlow::Break(unsupported(
242
+ "does not yet bind nested expression/correlated subqueries",
243
+ ))
244
+ }
245
+ fn pre_visit_expr(&mut self, expr: &Expr) -> ControlFlow<Self::Break> {
246
+ let result = match expr {
247
+ Expr::Identifier(ident) => self.column(None, &ident.value),
248
+ Expr::CompoundIdentifier(parts) if parts.len() >= 2 => self.column(
249
+ Some(TableKey::new(
250
+ &parts[..parts.len() - 1]
251
+ .iter()
252
+ .map(|i| i.to_string())
253
+ .collect::<Vec<_>>()
254
+ .join("."),
255
+ )),
256
+ &parts.last().unwrap().value,
257
+ ),
258
+ Expr::QualifiedWildcard(name, _) => self.qualifier(name),
259
+ Expr::Function(function) => {
260
+ if let FunctionArguments::List(args) = &function.args {
261
+ for arg in &args.args {
262
+ let arg = match arg {
263
+ FunctionArg::Unnamed(arg)
264
+ | FunctionArg::Named { arg, .. }
265
+ | FunctionArg::ExprNamed { arg, .. } => arg,
266
+ };
267
+ if let FunctionArgExpr::QualifiedWildcard(name) = arg
268
+ && let Err(error) = self.qualifier(name)
269
+ {
270
+ return ControlFlow::Break(error);
271
+ }
272
+ }
273
+ }
274
+ Ok(())
275
+ }
276
+ _ => Ok(()),
277
+ };
278
+ match result {
279
+ Ok(()) => ControlFlow::Continue(()),
280
+ Err(error) => ControlFlow::Break(error),
281
+ }
282
+ }
283
+ }
284
+ match node.visit(&mut Binder { relations, outputs }) {
285
+ ControlFlow::Continue(()) => Ok(()),
286
+ ControlFlow::Break(error) => Err(error),
287
+ }
288
+ }
289
+
290
+ #[cfg(test)]
291
+ mod tests {
292
+ use super::*;
293
+ fn catalog() -> TableCatalog {
294
+ let mut c = TableCatalog::default();
295
+ c.register_table("s.users", vec!["id".into(), "display name".into()]);
296
+ c.register_table("roles", vec!["id".into(), "enabled".into()]);
297
+ c
298
+ }
299
+ #[test]
300
+ fn dependencies_are_checked_before_output_aliases_are_trusted() {
301
+ let c = catalog();
302
+ for sql in [
303
+ "SELECT ghost.id AS id FROM ghost",
304
+ "SELECT u.missing AS id FROM s.users u",
305
+ "SELECT u.id AS id FROM s.users u WHERE u.missing > 0",
306
+ "SELECT u.id AS id FROM s.users u JOIN roles r ON u.id=r.missing",
307
+ "SELECT u.id AS id FROM s.users u ORDER BY absent",
308
+ "SELECT id AS output FROM s.users u JOIN roles r ON u.id=r.id",
309
+ "SELECT COUNT(ghost.*) AS total FROM s.users",
310
+ "SELECT a.id AS id FROM s.users a JOIN roles b ON c.id=a.id JOIN s.users c ON TRUE",
311
+ ] {
312
+ assert!(bound_projection(&c, sql).is_err(), "{sql}");
313
+ }
314
+ }
315
+ #[test]
316
+ fn proven_physical_scopes_and_output_order_aliases_bind() {
317
+ let c = catalog();
318
+ for sql in [
319
+ "(SELECT id FROM s.users WHERE id > 0)",
320
+ "SELECT u.\"display name\" AS label FROM \"s\".\"users\" AS u ORDER BY label",
321
+ "SELECT u.id AS uid FROM s.users u JOIN roles r ON u.id=r.id WHERE r.enabled",
322
+ "SELECT MAX(id) AS id FROM s.users HAVING MAX(id) > 0",
323
+ "SELECT 1 AS id",
324
+ "SELECT a.id AS id FROM s.users a JOIN roles b ON b.id=a.id JOIN s.users c ON c.id=b.id",
325
+ "SELECT a.id AS id FROM s.users a JOIN roles b ON enabled JOIN roles c ON c.id=b.id",
326
+ ] {
327
+ assert!(bound_projection(&c, sql).is_ok(), "{sql}");
328
+ }
329
+ }
330
+ #[test]
331
+ fn unknown_scope_shapes_do_not_silently_skip_dependency_validation() {
332
+ let c = catalog();
333
+ for sql in [
334
+ "WITH d AS (SELECT id FROM ghost) SELECT id FROM d",
335
+ "SELECT d.id FROM (SELECT id FROM ghost) d",
336
+ "SELECT (SELECT ghost.id FROM ghost) AS id",
337
+ "SELECT u.id FROM s.users u JOIN roles r USING (id)",
338
+ ] {
339
+ assert!(bound_projection(&c, sql).is_err(), "{sql}");
340
+ }
341
+ }
342
+ }
@@ -16,7 +16,7 @@ use crate::policy::{PolicyIr, Resolution};
16
16
  use crate::policy_compile::{
17
17
  ParsedPolicyConstraint, parse_policy_constraint, parse_policy_constraint_with_registry,
18
18
  };
19
- use crate::sql::collect_query_projection_column_names;
19
+ mod dimension;
20
20
 
21
21
  /// Catalog facts supplied at policy registration time (from any adapter snapshot).
22
22
  #[derive(Debug, Default, Clone)]
@@ -178,9 +178,6 @@ impl TableCatalog {
178
178
  policy: &PolicyIr,
179
179
  registry: &AggregateRegistry,
180
180
  ) -> Result<(), RewriteError> {
181
- if !self.loaded {
182
- return Ok(());
183
- }
184
181
  let PolicyIr::Pgn {
185
182
  sources,
186
183
  dimension_tables,
@@ -215,9 +212,6 @@ impl TableCatalog {
215
212
  parsed: &ParsedPolicyConstraint,
216
213
  registry: &AggregateRegistry,
217
214
  ) -> Result<(), RewriteError> {
218
- if !self.loaded {
219
- return Ok(());
220
- }
221
215
  let PolicyIr::Pgn {
222
216
  sources,
223
217
  dimension_tables,
@@ -391,13 +385,11 @@ fn validate_pgn_policy_parsed(
391
385
  dimension_columns.insert(TableKey::new(base), columns);
392
386
  }
393
387
  for (alias, query_sql) in dimension_queries {
394
- let columns = collect_query_projection_column_names(query_sql)
388
+ let columns = dimension::bound_projection(catalog, query_sql)
395
389
  .map_err(|err| {
396
390
  RewriteError::catalog_with_context(
397
- ErrorKind::UnknownColumn,
398
- format!(
399
- "Dimension subquery '{alias}' has invalid projection for catalog validation: {err}"
400
- ),
391
+ err.kind(),
392
+ format!("Dimension subquery '{alias}' failed catalog binding: {err}"),
401
393
  Some(alias.clone()),
402
394
  None,
403
395
  Some(constraint.to_string()),
@@ -1083,7 +1075,8 @@ mod tests {
1083
1075
 
1084
1076
  #[test]
1085
1077
  fn validates_constraint_columns_through_dimension_subquery_alias() {
1086
- let catalog = sample_catalog();
1078
+ let mut catalog = sample_catalog();
1079
+ catalog.register_table("session_user", vec!["user_id".into()]);
1087
1080
  let mut dimension_aliases = HashMap::new();
1088
1081
  dimension_aliases.insert("u".to_string(), "u".to_string());
1089
1082
  let mut dimension_queries = HashMap::new();
@@ -1111,7 +1104,8 @@ mod tests {
1111
1104
 
1112
1105
  #[test]
1113
1106
  fn rejects_unknown_dimension_subquery_column() {
1114
- let catalog = sample_catalog();
1107
+ let mut catalog = sample_catalog();
1108
+ catalog.register_table("session_user", vec!["user_id".into()]);
1115
1109
  let mut dimension_aliases = HashMap::new();
1116
1110
  dimension_aliases.insert("u".to_string(), "u".to_string());
1117
1111
  let mut dimension_queries = HashMap::new();
@@ -16,6 +16,8 @@ pub mod policy_index;
16
16
  mod policy_parser;
17
17
  pub mod policy_store;
18
18
  pub mod query_analysis;
19
+ pub mod resolution_placement;
20
+ pub mod resolution_registry;
19
21
  pub mod rewrite_stats;
20
22
  pub mod rewrite_strategy;
21
23
  pub mod rewriter;
@@ -79,3 +81,5 @@ pub use source_sets::{
79
81
  pub use sql::SqlDialect;
80
82
  pub use statement_tables::{statement_sink_key, statement_table_keys};
81
83
  pub use threshold::{prune_dominated_remove_policies, threshold_dominates};
84
+
85
+ pub use resolution_registry::{ResolutionPrecedence, ResolutionProperties, ResolutionRegistry};
@@ -97,6 +97,16 @@ impl RewriteEngine for PartialPushEngine {
97
97
  return Ok(RewriteAttempt::Skipped);
98
98
  }
99
99
 
100
+ // This lowering separates policy_eval from the public result. It does
101
+ // not yet carry final output bindings into that aggregate scope.
102
+ if let Some((policy_id, constraint)) =
103
+ crate::rewriter::output_scope::applicable_output_policy(rewriter, request.statement)
104
+ {
105
+ return Err(RewriteError::unsupported_statement(format!(
106
+ "partial-push policy evaluation does not yet bind final _OUTPUT_ attributes for policy {policy_id} ({constraint}); no-SINK output policy requires a supported final-output endpoint",
107
+ )));
108
+ }
109
+
100
110
  let is_aggregation = request
101
111
  .analysis
102
112
  .select_scopes