use crate::db::schema::PersistedIndexExpressionOp;
#[must_use]
pub(in crate::db) const fn index_expression_supports_text_casefold_lookup(
op: PersistedIndexExpressionOp,
) -> bool {
matches!(op, PersistedIndexExpressionOp::Lower)
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) struct SemanticIndexExpression {
op: PersistedIndexExpressionOp,
field: String,
}
impl SemanticIndexExpression {
#[must_use]
pub(in crate::db) const fn new(op: PersistedIndexExpressionOp, field: String) -> Self {
Self { op, field }
}
#[must_use]
pub(in crate::db) const fn field(&self) -> &str {
self.field.as_str()
}
#[must_use]
pub(in crate::db) const fn op(&self) -> PersistedIndexExpressionOp {
self.op
}
#[must_use]
pub(in crate::db) const fn supports_text_casefold_lookup(&self) -> bool {
index_expression_supports_text_casefold_lookup(self.op)
}
#[must_use]
pub(in crate::db) fn canonical_order_text(&self) -> String {
self.canonical_order_parts().concat()
}
#[must_use]
pub(in crate::db) fn matches_canonical_order_text(&self, text: &str) -> bool {
let mut remaining = text;
for part in self.canonical_order_parts() {
let Some(suffix) = remaining.strip_prefix(part) else {
return false;
};
remaining = suffix;
}
remaining.is_empty()
}
const fn canonical_order_parts(&self) -> [&str; 3] {
let (prefix, suffix) = match self.op {
PersistedIndexExpressionOp::Lower => ("LOWER(", ")"),
PersistedIndexExpressionOp::Upper => ("UPPER(", ")"),
PersistedIndexExpressionOp::Trim => ("TRIM(", ")"),
PersistedIndexExpressionOp::LowerTrim => ("LOWER(TRIM(", "))"),
PersistedIndexExpressionOp::Date => ("DATE(", ")"),
PersistedIndexExpressionOp::Year => ("YEAR(", ")"),
PersistedIndexExpressionOp::Month => ("MONTH(", ")"),
PersistedIndexExpressionOp::Day => ("DAY(", ")"),
};
[prefix, self.field(), suffix]
}
}
crate::retained::retained_fields!(SemanticIndexExpression {
Self{op,field} => [op,field],
});
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn canonical_expression_comparison_matches_exact_rendered_bytes() {
use PersistedIndexExpressionOp::{Date, Day, Lower, LowerTrim, Month, Trim, Upper, Year};
for (op, prefix, suffix) in [
(Lower, "LOWER(", ")"),
(Upper, "UPPER(", ")"),
(Trim, "TRIM(", ")"),
(LowerTrim, "LOWER(TRIM(", "))"),
(Date, "DATE(", ")"),
(Year, "YEAR(", ")"),
(Month, "MONTH(", ")"),
(Day, "DAY(", ")"),
] {
for field in ["name", "账户.名", "odd)field(", ""] {
let expression = SemanticIndexExpression::new(op, field.to_string());
let expected = format!("{prefix}{field}{suffix}");
assert_eq!(expression.canonical_order_text(), expected);
assert!(expression.matches_canonical_order_text(&expected));
for boundary in 0..expected.len() {
if let Some(truncated) = expected.get(..boundary) {
assert!(!expression.matches_canonical_order_text(truncated));
}
}
assert!(!expression.matches_canonical_order_text(&format!("{expected}x")));
assert!(!expression.matches_canonical_order_text(&expected.to_lowercase()));
}
}
}
}