Repository: pola-rs/polars
File: crates/polars-plan/src/plans/visitor/hash.rs (Rust)

Code before:
    }
}

fn hash_exprs<H: Hasher>(exprs: &[ExprIR], expr_arena: &Arena<AExpr>, state: &mut H) {
    for e in exprs {
        e.traverse_and_hash(expr_arena, state);
    }
}

impl Hash for HashableEqLP<'_> {
    // This hashes the variant, not the whole plan
    fn hash<H: Hasher>(&self, state: &mut H) {
        let alp = self.node.to_alp(self.lp_arena);
        std::mem::discriminant(alp).hash(state);
        match alp {
            #[cfg(feature = "python")]
            IR::PythonScan { options } => {
                // Hash the Python function object using the pointer to the object
                // This should be the same as calling id() in python, but we don't need the GIL
                if let Some(scan_fn) = &options.scan_fn {
                    let ptr_addr = scan_fn.0.as_ptr() as usize;
                    ptr_addr.hash(state);
                }
                // Hash the stable fields
                // We include the schema since it can be set by the user
                options.schema.hash(state);
                options.python_source.hash(state);
                options.validate_schema.hash(state);
            },
            IR::Slice {
                offset,
                len,
                input: _,
            } => {
                len.hash(state);
                offset.hash(state);
            },
            IR::Filter {
                input: _,
                predicate,
            } => {
                predicate.traverse_and_hash(self.expr_arena, state);
            },
            IR::Scan {
                sources,
                file_info: _,
                hive_parts: _,
                predicate,
                output_schema: _,
                scan_type,
                unified_scan_args,

Review comment (bug):
Didn't pull and test the PR yet, but this for sure also needs to check the `n_rows`, `predicate` and other fields.

I think because of the optimiser order it should go fine here, but you should probably add a test to ensure that io plugin sources with slices and predicates don't get CSE-ed together with incorrect slicing / filtering.

Code after:
    if let Some(e) = expr {
        e.traverse_and_hash(expr_arena, state)
    }
}

fn hash_exprs<H: Hasher>(exprs: &[ExprIR], expr_arena: &Arena<AExpr>, state: &mut H) {
    for e in exprs {
        e.traverse_and_hash(expr_arena, state);
    }
}

#[cfg(feature = "python")]
fn hash_python_predicate<H: Hasher>(
    pred: &crate::prelude::PythonPredicate,
    expr_arena: &Arena<AExpr>,
    state: &mut H,
) {
    use crate::prelude::PythonPredicate;
    std::mem::discriminant(pred).hash(state);
    match pred {
        PythonPredicate::None => {},
        PythonPredicate::PyArrow(s) => s.hash(state),
        PythonPredicate::Polars(e) => e.traverse_and_hash(expr_arena, state),
    }
}

#[cfg(feature = "python")]
fn pred_eq(
    l: &crate::prelude::PythonPredicate,
    r: &crate::prelude::PythonPredicate,
    expr_arena: &Arena<AExpr>,
) -> bool {
    use crate::prelude::PythonPredicate;
    match (l, r) {
        (PythonPredicate::None, PythonPredicate::None) => true,
        (PythonPredicate::PyArrow(a), PythonPredicate::PyArrow(b)) => a == b,
        (PythonPredicate::Polars(a), PythonPredicate::Polars(b)) => expr_ir_eq(a, b, expr_arena),
        _ => false,
    }
}

impl Hash for HashableEqLP<'_> {
    // This hashes the variant, not the whole plan
    fn hash<H: Hasher>(&self, state: &mut H) {
        let alp = self.node.to_alp(self.lp_arena);
        std::mem::discriminant(alp).hash(state);
        match alp {
            #[cfg(feature = "python")]
            IR::PythonScan {
                options:
                    PythonOptions {