kevy 6.4.0

kevy — a pure-Rust, zero-dependency, Redis-compatible KV server.
Documentation
//! Turning a MATCH's clauses into what the segment takes: `IN` names
//! into field positions, `FILTER` predicates into tests. Split from
//! `ops.rs` for the 500-LOC house rule (a `#[path]` child, so it shares
//! the op module's imports of the wire statuses).
//!
//! Both need the index spec, which only the shard holding the segment
//! has — which is also why the errors are built here, carrying the whole
//! explanation rather than a status the origin would have to guess at.

/// Map an `IN <field…>` clause's names onto the segment's field
/// positions, in declaration order.
///
/// `Err` carries a ready-made error chunk naming the field that is not
/// declared, and listing the ones that are. Scoping to an undeclared
/// field could just match nothing — but a typo in a field name would
/// then return an empty result that looks exactly like a working query
/// over a corpus with no hits, which is the one failure mode a search
/// engine must not have.
pub(super) fn scope_positions(
    spec: &kevy_index::IndexSpec,
    scope: &[Vec<u8>],
) -> Result<Vec<usize>, Vec<u8>> {
    let mut out = Vec::with_capacity(scope.len());
    for want in scope {
        match spec.fields.iter().position(|f| f.name == *want) {
            Some(i) => out.push(i),
            None => {
                let declared: Vec<&[u8]> = spec.fields.iter().map(|f| f.name.as_slice()).collect();
                return Err(clause_error("IN", want, "index", &declared));
            }
        }
    }
    Ok(out)
}

/// A per-shard chunk explaining a clause this index cannot answer,
/// naming what it DOES offer. Only the shard holding the spec knows
/// that, which is why the explanation is built here and carried whole.
fn clause_error(clause: &str, bad: &[u8], verb: &str, offered: &[&[u8]]) -> Vec<u8> {
    let mut chunk = vec![crate::cmd_index_query::ST_CLAUSE];
    chunk.extend_from_slice(clause_text(clause, bad, verb, offered).as_bytes());
    chunk
}

/// [`clause_error`]'s field-not-STORED sibling: same explanation, but
/// tagged ST_NOFIELD with the field carried structurally, so the
/// origin reduce can feed the advise log without parsing prose.
fn nofield_error(clause: &str, bad: &[u8], offered: &[&[u8]]) -> Vec<u8> {
    let mut chunk = vec![crate::cmd_index_query::ST_NOFIELD];
    let f = &bad[..bad.len().min(255)];
    chunk.push(f.len() as u8);
    chunk.extend_from_slice(f);
    chunk.extend_from_slice(clause_text(clause, bad, "store", offered).as_bytes());
    chunk
}

fn clause_text(clause: &str, bad: &[u8], verb: &str, offered: &[&[u8]]) -> String {
    format!(
        "{clause} names field '{}', which this index does not {verb} — it {}: {}",
        String::from_utf8_lossy(bad),
        // Third person singular: a sibilant takes -es ("indexes"), anything
        // else takes -s ("stores"). `{verb}es` for both printed "storees".
        if verb.ends_with(['s', 'x', 'z']) { format!("{verb}es") } else { format!("{verb}s") },
        String::from_utf8_lossy(&offered.join(&b", "[..])),
    )
}

/// [`filter_preds`] with each test boxed as the closure the segment
/// takes. The boxes must outlive the borrowed `Filter` list, so they are
/// returned rather than built inline.
type ValuePred = Box<dyn Fn(&[u8]) -> bool>;
type BoxedPred = (usize, ValuePred);

pub(super) fn boxed_preds(
    spec: &kevy_index::IndexSpec,
    filters: &[super::args::FilterArg],
    now: i64,
) -> Result<Vec<BoxedPred>, Vec<u8>> {
    Ok(filter_tests(spec, filters, now)?
        .into_iter()
        .map(|(field, t)| {
            let f: ValuePred = Box::new(move |v: &[u8]| t.passes(v));
            (field, f)
        })
        .collect())
}

/// Build the segment-level predicates for a `FILTER` clause: map each
/// named field onto its stored-value position and build the comparison
/// with the type that field was DECLARED as.
///
/// The comparison itself lives with the spec (`ValueTest`), so the server
/// and the embedded store cannot disagree about what a bound means.
pub(super) fn filter_tests(
    spec: &kevy_index::IndexSpec,
    filters: &[super::args::FilterArg],
    now: i64,
) -> Result<Vec<(usize, kevy_index::ValueTest)>, Vec<u8>> {
    use super::args::FilterShape;
    let mut out = Vec::with_capacity(filters.len());
    for f in filters {
        let Some(pos) = spec.values.iter().position(|v| v.name == f.field) else {
            let stored: Vec<&[u8]> = spec.values.iter().map(|v| v.name.as_slice()).collect();
            return Err(nofield_error("FILTER", &f.field, &stored));
        };
        let ty = spec.values[pos].ty;
        let (test, raw) = match &f.shape {
            FilterShape::Range { min, max } => {
                (kevy_index::ValueTest::range_at(ty, min, max, now), min)
            }
            FilterShape::Eq { value } => (kevy_index::ValueTest::eq_at(ty, value, now), value),
        };
        let Some(test) = test else {
            let mut chunk = vec![crate::cmd_index_query::ST_CLAUSE];
            chunk.extend_from_slice(
                format!(
                    "FILTER bound '{}' is not a valid {}, which is how this index declares '{}'",
                    String::from_utf8_lossy(raw),
                    ty.tag(),
                    String::from_utf8_lossy(&f.field),
                )
                .as_bytes(),
            );
            return Err(chunk);
        };
        out.push((pos, test));
    }
    Ok(out)
}

/// Resolve a `SORT <field> ASC|DESC` clause: which stored value orders
/// the page, and the order-preserving encoding of that field's type.
///
/// Errors when the field is not stored, naming what is — the same
/// contract `IN` and `FILTER` keep.
/// Resolve a `DISTINCT <field>` clause to the stored-value position and
/// that field's declared type. Errors when the field is not stored,
/// naming what is.
pub(super) fn distinct_field(
    spec: &kevy_index::IndexSpec,
    distinct: &Option<Vec<u8>>,
) -> Result<Option<(usize, kevy_index::ValType)>, Vec<u8>> {
    let Some(field) = distinct else { return Ok(None) };
    let Some(pos) = spec.values.iter().position(|v| v.name == *field) else {
        let stored: Vec<&[u8]> = spec.values.iter().map(|v| v.name.as_slice()).collect();
        return Err(nofield_error("DISTINCT", field, &stored));
    };
    Ok(Some((pos, spec.values[pos].ty)))
}

/// Resolve a `FACET <field…>` clause: each named field's stored-value
/// position and declared type. Errors when one is not stored, naming
/// what is.
pub(super) fn facet_fields(
    spec: &kevy_index::IndexSpec,
    facets: &[Vec<u8>],
) -> Result<Vec<(usize, kevy_index::ValType)>, Vec<u8>> {
    let mut out = Vec::with_capacity(facets.len());
    for field in facets {
        let Some(pos) = spec.values.iter().position(|v| v.name == *field) else {
            let stored: Vec<&[u8]> = spec.values.iter().map(|v| v.name.as_slice()).collect();
            return Err(nofield_error("FACET", field, &stored));
        };
        out.push((pos, spec.values[pos].ty));
    }
    Ok(out)
}

pub(super) fn sort_field(
    spec: &kevy_index::IndexSpec,
    sort: &Option<(Vec<u8>, bool)>,
) -> Result<Option<(usize, bool, kevy_index::ValType)>, Vec<u8>> {
    let Some((field, desc)) = sort else { return Ok(None) };
    let Some(pos) = spec.values.iter().position(|v| v.name == *field) else {
        let stored: Vec<&[u8]> = spec.values.iter().map(|v| v.name.as_slice()).collect();
        return Err(nofield_error("SORT", field, &stored));
    };
    Ok(Some((pos, *desc, spec.values[pos].ty)))
}