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.
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/Cargo.lock +3 -3
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/Cargo.toml +1 -1
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/PKG-INFO +1 -1
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/benches/rewrite_perf.rs +31 -4
- data_flow_control-0.2.1/passant-core/src/catalog/dimension.rs +342 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/catalog.rs +8 -14
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/lib.rs +4 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/partial_push.rs +10 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/planner.rs +112 -20
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy.rs +47 -3
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy_index.rs +1 -1
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy_parser.rs +7 -4
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy_pgn.pest +1 -1
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy_store/branch.rs +4 -2
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy_store/compiled.rs +5 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy_store/indexes.rs +42 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy_store/mod.rs +110 -47
- data_flow_control-0.2.1/passant-core/src/resolution_placement.rs +67 -0
- data_flow_control-0.2.1/passant-core/src/resolution_registry.rs +117 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewrite_stats.rs +4 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewrite_strategy.rs +38 -8
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/aggregates.rs +1 -1
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/columns.rs +148 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/helpers.rs +5 -1
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/insert_output_policy.rs +1 -11
- data_flow_control-0.2.1/passant-core/src/rewriter/joint_lineage/terminal_distinct.rs +284 -0
- data_flow_control-0.2.1/passant-core/src/rewriter/joint_lineage/terminal_except.rs +187 -0
- data_flow_control-0.2.1/passant-core/src/rewriter/joint_lineage/terminal_intersect.rs +148 -0
- data_flow_control-0.2.1/passant-core/src/rewriter/joint_lineage.rs +1968 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/limit.rs +29 -6
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/mod.rs +108 -18
- data_flow_control-0.2.1/passant-core/src/rewriter/output_scope.rs +482 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/plan/actions.rs +39 -7
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/plan/mod.rs +30 -5
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/plan/scope_plan.rs +21 -2
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/policy_expr.rs +5 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/resolution.rs +589 -1
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/select.rs +113 -9
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/types.rs +85 -1
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/write_path.rs +35 -7
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/source_set_index.rs +2 -2
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/source_sets/branch.rs +1 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/source_sets/mod.rs +33 -15
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/source_sets/rewrites.rs +45 -106
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/source_sets/split.rs +6 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/sql/builders.rs +6 -2
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/common/duckdb.rs +40 -1
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/common/mod.rs +78 -3
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/conformance.rs +47 -12
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/partial_push.rs +13 -2
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/pgn.rs +17 -4
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/pgn_unique.rs +15 -2
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/policy_storage.rs +15 -2
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/rewrite_api_parity.rs +19 -3
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/semiring.rs +27 -2
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/source_sets.rs +59 -16
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/symmetric_self_join.rs +12 -2
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/threshold.rs +11 -3
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/unique_not_unique.rs +19 -3
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/dialect.rs +13 -3
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/execution/insert.rs +2 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/execution/kill.rs +1 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/execution/oracle.rs +11 -11
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/explain_strategy.rs +29 -5
- data_flow_control-0.2.1/passant-core/tests/gaps/column_propagation.rs +96 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/gaps/dimensions.rs +10 -1
- data_flow_control-0.2.1/passant-core/tests/gaps/exists_join.rs +79 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/gaps/having_where.rs +13 -1
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/gaps/identifiers.rs +13 -2
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/gaps/insert_shapes.rs +32 -11
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/gaps/multi_source.rs +25 -7
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/gaps/query_shapes.rs +20 -4
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/gaps/scalar_subquery.rs +10 -1
- data_flow_control-0.2.1/passant-core/tests/indexed_candidate_differential.rs +156 -0
- data_flow_control-0.2.1/passant-core/tests/kill_null_guard_regression.rs +64 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/paper_examples.rs +30 -17
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/planner.rs +156 -79
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/policy_store_memory.rs +52 -11
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/rewrite/derived_policy.rs +39 -18
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/rewrite/in_semijoin.rs +23 -7
- data_flow_control-0.2.1/passant-core/tests/rewrite/insert.rs +188 -0
- data_flow_control-0.2.1/passant-core/tests/rewrite/joins.rs +508 -0
- data_flow_control-0.2.1/passant-core/tests/rewrite/main.rs +37 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/rewrite/meal_deduction.rs +3 -3
- data_flow_control-0.2.1/passant-core/tests/rewrite/output.rs +112 -0
- data_flow_control-0.2.1/passant-core/tests/rewrite/recursive.rs +253 -0
- data_flow_control-0.2.1/passant-core/tests/rewrite/scan.rs +501 -0
- data_flow_control-0.2.1/passant-core/tests/rewrite/update.rs +122 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/rewrite_candidate_regression.rs +101 -35
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/rewrite_fast_paths.rs +12 -2
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/rewrite_large_registry.rs +47 -15
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/rewrite_stats_timings.rs +8 -2
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/scan_ready_expr.rs +18 -6
- data_flow_control-0.2.1/passant-core/tests/source_set_split_no_parse.rs +73 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/ui_resolution.rs +54 -20
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-py/src/bindings/planner.rs +43 -27
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/pyproject.toml +1 -1
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/__init__.py +7 -4
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/_rust.py +4 -1
- data_flow_control-0.2.1/python/data_flow_control/adapters/_retry.py +58 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/adapters/base.py +3 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/adapters/datafusion.py +24 -2
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/adapters/duckdb.py +58 -4
- data_flow_control-0.2.1/python/data_flow_control/adapters/postgres.py +115 -0
- data_flow_control-0.2.1/python/data_flow_control/adapters/sqlite.py +142 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/connection.py +43 -3
- data_flow_control-0.2.1/python/data_flow_control/errors.py +48 -0
- data_flow_control-0.2.1/python/data_flow_control/langchain/__init__.py +24 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/langchain/database.py +11 -4
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/planner.py +16 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/policy.py +15 -0
- data_flow_control-0.2.1/python/data_flow_control/tools/__init__.py +8 -0
- data_flow_control-0.2.1/python/data_flow_control/tools/retry.py +461 -0
- data_flow_control-0.1.8/passant-core/tests/gaps/column_propagation.rs +0 -83
- data_flow_control-0.1.8/passant-core/tests/gaps/exists_join.rs +0 -64
- data_flow_control-0.1.8/passant-core/tests/indexed_candidate_differential.rs +0 -108
- data_flow_control-0.1.8/passant-core/tests/rewrite/insert.rs +0 -159
- data_flow_control-0.1.8/passant-core/tests/rewrite/joins.rs +0 -414
- data_flow_control-0.1.8/passant-core/tests/rewrite/main.rs +0 -21
- data_flow_control-0.1.8/passant-core/tests/rewrite/output.rs +0 -85
- data_flow_control-0.1.8/passant-core/tests/rewrite/recursive.rs +0 -213
- data_flow_control-0.1.8/passant-core/tests/rewrite/scan.rs +0 -432
- data_flow_control-0.1.8/passant-core/tests/rewrite/update.rs +0 -102
- data_flow_control-0.1.8/passant-core/tests/source_set_split_no_parse.rs +0 -60
- data_flow_control-0.1.8/python/data_flow_control/adapters/postgres.py +0 -67
- data_flow_control-0.1.8/python/data_flow_control/adapters/sqlite.py +0 -73
- data_flow_control-0.1.8/python/data_flow_control/langchain/__init__.py +0 -15
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/LICENSE +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/Cargo.toml +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/aggregate_registry.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/diagnostics.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/explain.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/full_push.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/identifiers.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/intern.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/ir.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/optimizer.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/parser.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy_compile.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy_store/delete.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/policy_store/memory.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/query_analysis.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/constraint_preprocess.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/derived_policy.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/dimensions.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/exists.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/expr.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/plan/applicability.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/plan/dominance.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/plan/write_plan.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/projection.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/rewriter/scope.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/semiring.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/source_sets/analysis.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/source_sets/annotations.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/sql/ast_stats.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/sql/columns.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/sql/dialect.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/sql/expr.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/sql/expr_key.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/sql/mod.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/sql/parse.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/sql/render.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/statement_tables.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/src/threshold.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/completion/main.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/execution/main.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/execution/remove.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/execution/update.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/expected/duckdb/remove_scan.sql +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/expected/postgres/remove_scan.sql +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/expected/sqlite/remove_scan.sql +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/gaps/main.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/gaps/operators.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/insert_values_body.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/parser.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/policy.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/policy_dialect.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/sql_builders.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-core/tests/sql_columns.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-py/Cargo.toml +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-py/src/bindings/catalog.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-py/src/bindings/errors.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-py/src/bindings/mod.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-py/src/bindings/policy.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-py/src/bindings/stats.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/passant-py/src/lib.rs +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/adapters/__init__.py +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/adapters/clickhouse.py +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/adapters/kill.py +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/adapters/pg_catalog.py +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/adapters/registry.py +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/adapters/umbra.py +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/aggregate_introspection.py +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/catalog.py +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/dialect.py +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/langchain/agent.py +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/langchain/call_tool_with_dataflow.py +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/langchain/middleware.py +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/langchain/schema.py +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/langchain/sql_validation.py +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/langchain/tool_spec.py +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/langchain/wrapper.py +0 -0
- {data_flow_control-0.1.8 → data_flow_control-0.2.1}/python/data_flow_control/options.py +0 -0
- {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
|
|
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
|
|
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
|
|
1626
|
+
version = "0.2.1"
|
|
1627
1627
|
dependencies = [
|
|
1628
1628
|
"passant-core",
|
|
1629
1629
|
"pyo3",
|
|
@@ -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
|
|
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("
|
|
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("
|
|
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
|
|
393
|
+
store
|
|
394
|
+
.register(policy.clone(), &catalog, ®istry)
|
|
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
|
-
|
|
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 =
|
|
388
|
+
let columns = dimension::bound_projection(catalog, query_sql)
|
|
395
389
|
.map_err(|err| {
|
|
396
390
|
RewriteError::catalog_with_context(
|
|
397
|
-
|
|
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
|