use crate::catalog::ValType;
use crate::error::SpecError;
use crate::spec::IndexSpec;
use crate::value::{IndexValue, order_key};
use kevy_text::SortOrder;
#[path = "composite_where.rs"]
mod clause;
pub use clause::{WhereClause, composite_bounds, parse_where};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct CompositeCol {
pub name: Vec<u8>,
pub ty: ValType,
pub order: SortOrder,
}
impl CompositeCol {
pub fn new(name: impl Into<Vec<u8>>, ty: ValType) -> CompositeCol {
CompositeCol { name: name.into(), ty, order: SortOrder::Asc }
}
#[must_use]
pub fn with_order(mut self, order: SortOrder) -> CompositeCol {
self.order = order;
self
}
}
pub const MAX_COMPOSITE_COLS: usize = 8;
pub const MAX_STR_COMPONENT: usize = 255;
pub const WHERE_NOT_COMPOSITE: &str =
"WHERE requires a composite index (an ORDERPATH-compiled one) — this index is not one";
fn encode_component(col: &CompositeCol, raw: &[u8]) -> Option<Vec<u8>> {
let mut framed = match col.ty {
ValType::I64 | ValType::F64 => order_key(col.ty, raw)?,
ValType::Str => {
if raw.len() > MAX_STR_COMPONENT {
return None;
}
let mut out = Vec::with_capacity(raw.len() + 2);
for &b in raw {
out.push(b);
if b == 0x00 {
out.push(0xFF);
}
}
out.extend_from_slice(&[0x00, 0x00]);
out
}
ValType::Vector => return None,
};
if col.order == SortOrder::Desc {
for b in &mut framed {
*b = !*b;
}
}
Some(framed)
}
pub fn composite_encode(cols: &[CompositeCol], vals: &[Option<&[u8]>]) -> Option<Vec<u8>> {
composite_classify(cols, vals).into_value()
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RowDerivation {
Indexed(Vec<u8>),
Absent,
CoerceFailed,
Oversize,
}
impl RowDerivation {
fn into_value(self) -> Option<Vec<u8>> {
match self {
Self::Indexed(v) => Some(v),
_ => None,
}
}
}
pub fn composite_classify(cols: &[CompositeCol], vals: &[Option<&[u8]>]) -> RowDerivation {
let mut out = Vec::new();
for (col, raw) in cols.iter().zip(vals) {
let Some(raw) = raw else { return RowDerivation::Absent };
match classify_component(col, raw) {
RowDerivation::Indexed(bytes) => out.extend_from_slice(&bytes),
other => return other,
}
}
RowDerivation::Indexed(out)
}
fn classify_component(col: &CompositeCol, raw: &[u8]) -> RowDerivation {
if col.ty == ValType::Str && raw.len() > MAX_STR_COMPONENT {
return RowDerivation::Oversize;
}
match encode_component(col, raw) {
Some(bytes) => RowDerivation::Indexed(bytes),
None => RowDerivation::CoerceFailed,
}
}
pub(crate) fn composite_guard(spec: &IndexSpec) -> Result<(), SpecError> {
let Some(cols) = &spec.composite else { return Ok(()) };
if spec.kind != crate::IndexKind::Range {
return Err(SpecError::CompositeNeedsRange);
}
if spec.ty != ValType::Str {
return Err(SpecError::CompositeNeedsStr);
}
if !spec.values.is_empty() {
return Err(SpecError::CompositeWithValues);
}
if spec.fields.len() != 1 {
return Err(SpecError::CompositeFieldCount);
}
if cols.is_empty() {
return Err(SpecError::CompositeNoColumns);
}
if cols.len() > MAX_COMPOSITE_COLS {
return Err(SpecError::CompositeTooManyColumns);
}
if cols.iter().any(|c| matches!(c.ty, ValType::Vector)) {
return Err(SpecError::CompositeColumnType);
}
Ok(())
}
impl IndexSpec {
pub fn scalar_read_names(&self) -> Vec<&[u8]> {
let mut names: Vec<&[u8]> = match &self.composite {
Some(cols) => cols.iter().map(|c| c.name.as_slice()).collect(),
None => vec![self.field()],
};
names.extend(self.values.iter().map(|v| v.name.as_slice()));
names
}
pub fn primary_width(&self) -> usize {
self.composite.as_ref().map_or(1, Vec::len)
}
pub fn derive_scalar(&self, prim: &[Option<Vec<u8>>]) -> Option<IndexValue> {
match &self.composite {
Some(_) => match self.classify_scalar(prim) {
RowDerivation::Indexed(v) => Some(IndexValue::Str(v)),
_ => None,
},
None => IndexValue::coerce(self.ty, prim.first()?.as_deref()?),
}
}
pub fn derive_scalar_refs(&self, prim: &[Option<&[u8]>]) -> Option<IndexValue> {
match &self.composite {
Some(cols) => match composite_classify(cols, prim) {
RowDerivation::Indexed(v) => Some(IndexValue::Str(v)),
_ => None,
},
None => IndexValue::coerce(self.ty, prim.first().copied().flatten()?),
}
}
pub fn classify_scalar(&self, prim: &[Option<Vec<u8>>]) -> RowDerivation {
match &self.composite {
Some(cols) => {
let refs: Vec<Option<&[u8]>> = prim.iter().map(|o| o.as_deref()).collect();
composite_classify(cols, &refs)
}
None => match prim.first().and_then(|o| o.as_deref()) {
None => RowDerivation::Absent,
Some(raw) => match IndexValue::coerce(self.ty, raw) {
Some(_) => RowDerivation::Indexed(raw.to_vec()),
None => RowDerivation::CoerceFailed,
},
},
}
}
}
#[cfg(test)]
#[path = "composite_tests.rs"]
mod tests;