use crate::model::index::{IndexKeyItem, IndexKeyItemsRef, IndexModel};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum ExpressionOrderTerm<'a> {
Lower(&'a str),
Upper(&'a str),
}
impl<'a> ExpressionOrderTerm<'a> {
#[must_use]
pub(in crate::db) fn parse(term: &'a str) -> Option<Self> {
if let Some(field) = parse_expression_order_term_inner(term, "LOWER") {
return Some(Self::Lower(field));
}
if let Some(field) = parse_expression_order_term_inner(term, "UPPER") {
return Some(Self::Upper(field));
}
None
}
#[must_use]
pub(in crate::db) const fn field(self) -> &'a str {
match self {
Self::Lower(field) | Self::Upper(field) => field,
}
}
#[must_use]
pub(in crate::db) fn canonical_text_with_field(self, field: &str) -> String {
match self {
Self::Lower(_) => format!("LOWER({field})"),
Self::Upper(_) => format!("UPPER({field})"),
}
}
}
#[must_use]
pub(in crate::db) fn index_order_terms(index: &IndexModel) -> Vec<String> {
match index.key_items() {
IndexKeyItemsRef::Fields(fields) => {
fields.iter().map(|field| (*field).to_string()).collect()
}
IndexKeyItemsRef::Items(items) => items.iter().map(IndexKeyItem::canonical_text).collect(),
}
}
fn parse_expression_order_term_inner<'a>(term: &'a str, function: &str) -> Option<&'a str> {
let open_index = term.find('(')?;
if !term[..open_index].eq_ignore_ascii_case(function) || !term.ends_with(')') {
return None;
}
Some(&term[open_index.saturating_add(1)..term.len().saturating_sub(1)])
}
#[cfg(test)]
mod tests {
use crate::{
db::query::plan::{ExpressionOrderTerm, index_order_terms},
model::index::{IndexExpression, IndexKeyItem, IndexModel},
};
const EXPRESSION_INDEX_FIELDS: [&str; 1] = ["name"];
const EXPRESSION_INDEX_KEY_ITEMS: [IndexKeyItem; 1] =
[IndexKeyItem::Expression(IndexExpression::Lower("name"))];
const EXPRESSION_INDEX_MODEL: IndexModel = IndexModel::generated_with_key_items(
"order_term_tests::idx_name_lower",
"order_term_tests::Store",
&EXPRESSION_INDEX_FIELDS,
&EXPRESSION_INDEX_KEY_ITEMS,
false,
);
#[test]
fn expression_order_term_parse_recovers_supported_casefold_functions() {
assert_eq!(
ExpressionOrderTerm::parse("LOWER(name)"),
Some(ExpressionOrderTerm::Lower("name")),
);
assert_eq!(
ExpressionOrderTerm::parse("UPPER(name)"),
Some(ExpressionOrderTerm::Upper("name")),
);
assert_eq!(ExpressionOrderTerm::parse("TRIM(name)"), None);
}
#[test]
fn index_order_terms_use_canonical_key_item_text() {
assert_eq!(
index_order_terms(&EXPRESSION_INDEX_MODEL),
vec!["LOWER(name)".to_string()]
);
}
}