qraft-core 0.1.2

Core type system, query model, decoding, and SQL lowering primitives for qraft.
Documentation
//! Pattern-matching expressions and SQLite glob preparation.

use std::borrow::Cow;

use super::Operator;
use crate::{
    Bool, Likeable, LowerCompatible,
    expression::Expression,
    lower::{Instructions, LowerCtx},
};

/// A `like` predicate with optional negation and case-sensitivity.
#[derive(Debug)]
pub struct Like<L, R> {
    pub case_sensitive: bool,
    pub negated: bool,
    pub left: L,
    pub right: R,
}

/// Converts SQL `%` and `_` wildcards into the form expected by SQLite `glob`.
pub fn prepare_sqlite_glob<'a>(value: &'a str) -> Cow<'a, str> {
    let mut first_special = None;
    for (i, ch) in value.char_indices() {
        match ch {
            '*' | '?' | '%' | '_' => {
                first_special = Some(i);
                break;
            }
            _ => {}
        }
    }

    let Some(idx) = first_special else {
        return Cow::Borrowed(value);
    };

    let mut out = String::with_capacity(value.len());
    out.push_str(&value[..idx]);

    for ch in value[idx..].chars() {
        match ch {
            '*' => {
                out.push('[');
                out.push('*');
                out.push(']');
            }
            '?' => {
                out.push('[');
                out.push('?');
                out.push(']');
            }
            '%' => out.push('*'),
            '_' => out.push('?'),
            _ => out.push(ch),
        }
    }

    Cow::Owned(out)
}

#[qraft_expression_macro::as_expression]
impl<L, R, T> Expression for Like<L, R>
where
    T: Likeable,
    L: Expression<Type = T>,
    R: LowerCompatible<T>,
{
    type Type = Bool;

    fn lower(&self, ctx: &mut LowerCtx) -> usize {
        let lhs = self.left.lower(ctx);
        let rhs = self.right.lower_compatible(ctx);
        ctx.instrs.push_binary(
            Operator::Like {
                sensitive: self.case_sensitive,
                negated: self.negated,
            },
            lhs,
            rhs,
        );
        lhs + rhs + 1
    }
}

impl<L, R> Like<L, R> {
    /// Requests the case-sensitive variant where the dialect supports it.
    pub fn case_sensitive(mut self) -> Self {
        self.case_sensitive = true;
        self
    }
}

/// Adds `like` and `not like` helpers to string-like expressions.
pub trait LikeExt<T: Likeable>: Sized + Expression {
    fn like<E>(self, other: E) -> Like<Self, E>
    where
        E: LowerCompatible<T>,
    {
        Like {
            left: self,
            right: other,
            case_sensitive: false,
            negated: false,
        }
    }

    fn not_like<E>(self, other: E) -> Like<Self, E>
    where
        E: LowerCompatible<T>,
    {
        Like {
            left: self,
            right: other,
            case_sensitive: false,
            negated: true,
        }
    }
}

impl<T: Likeable, V> LikeExt<T> for V where V: Expression<Type = T> {}