#[cfg(test)]
mod tests;
use crate::{
MAX_INDEX_FIELDS,
db::{
index::{
EncodedValue, IndexId, IndexKey, IndexKeyKind, RawIndexStoreKey,
admit_query_index_component,
},
query::construction::ConstructionBudget,
},
error::InternalError,
value::Value,
};
use icydb_diagnostic_code::DiagnosticExecutionBudgetResource as Resource;
use std::ops::Bound;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum TextPrefixBoundMode {
Strict,
LowerOnly,
}
#[derive(Debug)]
pub(in crate::db) enum IndexRangeBoundEncodeError {
Lower,
Upper,
RawKey,
Construction(InternalError),
}
impl IndexRangeBoundEncodeError {
pub(in crate::db) fn into_internal_error(self) -> InternalError {
match self {
Self::Construction(error) => error,
Self::Lower | Self::Upper | Self::RawKey => InternalError::query_executor_invariant(),
}
}
}
pub(in crate::db) fn admit_index_prefix_bounds<C: AsRef<[u8]>>(
index_len: usize,
prefix: &[C],
budget: &dyn ConstructionBudget,
) -> Result<(), InternalError> {
if index_len > MAX_INDEX_FIELDS || prefix.len() > index_len {
return Err(InternalError::query_executor_invariant());
}
let capacity = IndexKey::raw_prefix_bounds_retained_capacity(index_len, prefix);
admit_raw_bound_capacity(capacity, budget)
}
fn admit_raw_bound_capacity(
capacity: usize,
budget: &dyn ConstructionBudget,
) -> Result<(), InternalError> {
budget.charge(Resource::TemporaryBytes, capacity as u64)?;
budget.charge(Resource::PredicateExpressionSteps, capacity as u64)
}
pub(in crate::db) struct IndexBoundsLowering {
lower: Bound<RawIndexStoreKey>,
upper: Bound<RawIndexStoreKey>,
encoded_prefix: Vec<EncodedValue>,
}
impl IndexBoundsLowering {
const fn new(
lower: Bound<RawIndexStoreKey>,
upper: Bound<RawIndexStoreKey>,
encoded_prefix: Vec<EncodedValue>,
) -> Self {
Self {
lower,
upper,
encoded_prefix,
}
}
pub(in crate::db) fn into_bounds_and_prefix_components(
self,
) -> (
Bound<RawIndexStoreKey>,
Bound<RawIndexStoreKey>,
Vec<Vec<u8>>,
) {
let prefix_components = self
.encoded_prefix
.into_iter()
.map(EncodedValue::into_bytes)
.collect();
(self.lower, self.upper, prefix_components)
}
}
#[must_use]
pub(in crate::db) fn starts_with_component_bounds(
prefix: &str,
mode: TextPrefixBoundMode,
) -> Option<(Bound<Value>, Bound<Value>)> {
if prefix.is_empty() {
return None;
}
let lower = Bound::Included(Value::Text(prefix.to_string()));
let upper = match mode {
TextPrefixBoundMode::Strict => next_text_prefix(prefix)
.map_or(Bound::Unbounded, |next| Bound::Excluded(Value::Text(next))),
TextPrefixBoundMode::LowerOnly => Bound::Unbounded,
};
Some((lower, upper))
}
pub(in crate::db) fn admit_text_prefix_bounds(
prefix: &str,
mode: TextPrefixBoundMode,
budget: &dyn ConstructionBudget,
) -> Result<(), InternalError> {
if prefix.is_empty() {
return Ok(());
}
let len = prefix.len() as u64;
let (backing, scan) = match mode {
TextPrefixBoundMode::Strict => (len.saturating_mul(2).saturating_add(1), len),
TextPrefixBoundMode::LowerOnly => (len, 0),
};
budget.charge(Resource::TemporaryBytes, backing)?;
budget.charge(
Resource::PredicateExpressionSteps,
backing.saturating_add(scan),
)
}
pub(in crate::db) fn build_index_prefix_bounds_for_encoded_components(
index_id: &IndexId,
key_kind: IndexKeyKind,
index_len: usize,
prefix: &[EncodedValue],
budget: &dyn ConstructionBudget,
) -> Result<(Bound<RawIndexStoreKey>, Bound<RawIndexStoreKey>), IndexRangeBoundEncodeError> {
admit_index_prefix_bounds(index_len, prefix, budget)
.map_err(IndexRangeBoundEncodeError::Construction)?;
let (lower, upper) =
raw_keys_for_component_prefix_with_kind(index_id, key_kind, index_len, prefix)?;
Ok((Bound::Included(lower), Bound::Included(upper)))
}
pub(in crate::db) fn raw_keys_for_component_prefix_with_kind<C: AsRef<[u8]>>(
index_id: &IndexId,
key_kind: IndexKeyKind,
index_len: usize,
prefix: &[C],
) -> Result<(RawIndexStoreKey, RawIndexStoreKey), IndexRangeBoundEncodeError> {
IndexKey::raw_bounds_for_prefix_with_kind(index_id, key_kind, index_len, prefix)
.map_err(|_| IndexRangeBoundEncodeError::RawKey)
}
fn raw_bounds_for_encoded_index_component_range(
index_id: &IndexId,
index_len: usize,
prefix: &[EncodedValue],
lower: &Bound<EncodedValue>,
upper: &Bound<EncodedValue>,
budget: &dyn ConstructionBudget,
) -> Result<(Bound<RawIndexStoreKey>, Bound<RawIndexStoreKey>), IndexRangeBoundEncodeError> {
if index_len == 0 || index_len > MAX_INDEX_FIELDS || prefix.len() >= index_len {
return Err(IndexRangeBoundEncodeError::RawKey);
}
let lower_component = encoded_component_bound(lower);
let upper_component = encoded_component_bound(upper);
let capacity = IndexKey::raw_component_range_bounds_capacity(
index_len,
prefix,
&lower_component,
&upper_component,
)
.map_err(|_| IndexRangeBoundEncodeError::RawKey)?;
admit_raw_bound_capacity(capacity, budget).map_err(IndexRangeBoundEncodeError::Construction)?;
IndexKey::raw_bounds_for_prefix_component_range_with_kind(
index_id,
IndexKeyKind::User,
index_len,
prefix,
&lower_component,
&upper_component,
)
.map_err(|_| IndexRangeBoundEncodeError::RawKey)
}
pub(in crate::db) fn build_index_component_range_with_encoded_prefix(
index_id: &IndexId,
index_len: usize,
encoded_prefix: Vec<EncodedValue>,
lower: &Bound<Value>,
upper: &Bound<Value>,
budget: &dyn ConstructionBudget,
) -> Result<IndexBoundsLowering, IndexRangeBoundEncodeError> {
let encoded_lower =
encode_semantic_component_bound(lower, IndexRangeBoundEncodeError::Lower, budget)?;
let encoded_upper =
encode_semantic_component_bound(upper, IndexRangeBoundEncodeError::Upper, budget)?;
let (lower, upper) = raw_bounds_for_encoded_index_component_range(
index_id,
index_len,
encoded_prefix.as_slice(),
&encoded_lower,
&encoded_upper,
budget,
)?;
Ok(IndexBoundsLowering::new(lower, upper, encoded_prefix))
}
pub(in crate::db) fn next_text_prefix(prefix: &str) -> Option<String> {
for (offset, character) in prefix.char_indices().rev() {
let Some(next_char) = next_unicode_scalar(character) else {
continue;
};
let mut successor = String::with_capacity(offset + next_char.len_utf8());
successor.push_str(&prefix[..offset]);
successor.push(next_char);
return Some(successor);
}
None
}
const fn encoded_component_bound(bound: &Bound<EncodedValue>) -> Bound<&[u8]> {
match bound {
Bound::Unbounded => Bound::Unbounded,
Bound::Included(value) => Bound::Included(value.encoded()),
Bound::Excluded(value) => Bound::Excluded(value.encoded()),
}
}
fn encode_semantic_component_bound(
bound: &Bound<Value>,
kind: IndexRangeBoundEncodeError,
budget: &dyn ConstructionBudget,
) -> Result<Bound<EncodedValue>, IndexRangeBoundEncodeError> {
if let Bound::Included(value) | Bound::Excluded(value) = bound {
admit_query_index_component(value, budget)
.map_err(IndexRangeBoundEncodeError::Construction)?;
}
match bound {
Bound::Unbounded => Ok(Bound::Unbounded),
Bound::Included(value) => EncodedValue::try_from_ref(value)
.map(Bound::Included)
.map_err(|_| kind),
Bound::Excluded(value) => EncodedValue::try_from_ref(value)
.map(Bound::Excluded)
.map_err(|_| kind),
}
}
fn next_unicode_scalar(value: char) -> Option<char> {
if value == char::MAX {
return None;
}
let mut next = u32::from(value).saturating_add(1);
if (0xD800..=0xDFFF).contains(&next) {
next = 0xE000;
}
char::from_u32(next)
}