hawdb-optimizer 0.4.0

An embedded Rust graph database for local data-plane workloads
Documentation
// Copyright 2026 Nowledge
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use super::*;

fn fixture() -> (
    RelationalJoinRewriteProblem,
    RelationalCsgCmpJoinImplementation,
) {
    let left = relation(A, 100);
    let right = relation(B, 200);
    let implementation = RelationalCsgCmpJoinImplementation {
        operator_id: RelationalJoinOperatorId::new(1),
        operator_kind: RelationalJoinOperatorKind::Inner,
        predicate_ids: vec![RelationalJoinPredicateId::new(1)],
        left_binding: A,
        left_access: RelationalJoinAccessPath::base(left.access_paths[0].descriptor.clone()),
        right_binding: B,
        right_access: RelationalJoinAccessPath::base(right.access_paths[0].descriptor.clone()),
        algorithm: RelationalEquiJoinAlgorithm::Hash,
        selectivity: RelationalJoinSelectivity::equi_join(Some(100), Some(200)),
    };
    let problem = RelationalJoinRewriteProblem {
        relations: vec![left, right],
        initial_tree: RelationalJoinTree::join(
            operator(1, RelationalJoinOperatorKind::Inner, &[A, B]),
            RelationalJoinTree::Relation(A),
            RelationalJoinTree::Relation(B),
        ),
        post_join_filter: None,
    };
    (problem, implementation)
}

fn enumerate(
    problem: &RelationalJoinRewriteProblem,
    implementations: &[RelationalCsgCmpJoinImplementation],
    required: &RequiredProperties,
    max_expressions: usize,
) -> Result<RelationalCsgCmpEnumeration, RelationalJoinRewriteError> {
    enumerate_relational_csg_cmp_joins_with_implementations(
        problem,
        required,
        RelationalJoinEnumerationConfig {
            max_expressions,
            ..RelationalJoinEnumerationConfig::default()
        },
        RelationalCsgCmpRightInputPolicy::AllowMaterialized,
        implementations,
    )
}

#[test]
fn costed_implementations_compete_and_charge_the_same_expression_budget() {
    let (problem, hash) = fixture();
    let old = enumerate(&problem, &[], &RequiredProperties::default(), 128).unwrap();
    let result = enumerate(
        &problem,
        std::slice::from_ref(&hash),
        &RequiredProperties::default(),
        128,
    )
    .unwrap();
    assert!(result.plan.cost().cost < old.plan.cost().cost);
    assert_eq!(result.plan.cost_breakdown.estimated_rows, 100);
    assert_eq!(result.plan.cost_breakdown.cpu, 908);
    assert_eq!(result.memo_expressions, old.memo_expressions + 1);
    let exact = result.memo_expressions;
    assert_eq!(
        enumerate(
            &problem,
            std::slice::from_ref(&hash),
            &RequiredProperties::default(),
            exact
        )
        .unwrap(),
        result
    );
    assert!(matches!(
        enumerate(&problem, std::slice::from_ref(&hash), &RequiredProperties::default(), exact - 1),
        Err(RelationalJoinRewriteError::Enumeration(RelationalJoinEnumerationError::ExpressionBudgetExceeded {
            required_expressions, max_expressions,
        })) if required_expressions == exact && max_expressions == exact - 1
    ));
    let mut merge = hash.clone();
    merge.algorithm = RelationalEquiJoinAlgorithm::Merge;
    let result = enumerate(
        &problem,
        &[hash, merge],
        &RequiredProperties::default(),
        128,
    )
    .unwrap();
    let RelationalCsgCmpPlanNode::Join {
        implementation: Some(selected),
        ..
    } = result.plan.root
    else {
        panic!("expected costed implementation")
    };
    assert_eq!(selected.algorithm, RelationalEquiJoinAlgorithm::Merge);
    assert_eq!(result.plan.cost_breakdown.cpu, 708);
}

#[test]
fn implementations_cannot_cross_binding_operator_or_predicate_boundaries() {
    let (problem, hash) = fixture();
    let old = enumerate(&problem, &[], &RequiredProperties::default(), 128).unwrap();
    for fault in 0..4 {
        let mut invalid = hash.clone();
        match fault {
            0 => invalid.left_binding = C,
            1 => invalid.operator_id = RelationalJoinOperatorId::new(2),
            2 => invalid.predicate_ids.clear(),
            _ => invalid.operator_kind = RelationalJoinOperatorKind::LeftOuter,
        }
        let result = enumerate(&problem, &[invalid], &RequiredProperties::default(), 128).unwrap();
        assert_eq!(result.plan, old.plan);
    }
}

#[test]
fn grace_hash_does_not_claim_left_ordering() {
    let (mut problem, mut hash) = fixture();
    let required = RequiredProperties {
        ordering: vec!["key".to_owned()],
        ..RequiredProperties::default()
    };
    for relation in &mut problem.relations {
        relation.access_paths[0].properties.ordering = required.ordering.clone();
    }
    hash.left_access.properties.ordering = required.ordering.clone();
    let result = enumerate(&problem, &[hash.clone()], &required, 128).unwrap();
    let RelationalCsgCmpPlanNode::Join { implementation, .. } = &result.plan.root else {
        panic!("expected join")
    };
    assert!(implementation.is_none());
    assert!(result.plan.properties.satisfies(&required));
    hash.algorithm = RelationalEquiJoinAlgorithm::Merge;
    let result = enumerate(&problem, &[hash], &required, 128).unwrap();
    let RelationalCsgCmpPlanNode::Join { implementation, .. } = &result.plan.root else {
        panic!("expected join")
    };
    assert!(implementation.is_some());
}