use crate::{
db::{
access::{SemanticIndexExpression, SemanticIndexKeyItemRef},
index::IndexExpressionSourceClass,
predicate::CoercionId,
scalar_expr::{
ScalarExprValue, derive_non_null_scalar_expression_value, scalar_expr_value_into_value,
},
schema::PersistedIndexExpressionOp,
},
value::Value,
};
#[must_use]
pub(in crate::db::query::plan) fn key_item_matches_field_and_coercion<'a>(
key_item: impl Into<SemanticIndexKeyItemRef<'a>>,
field: &str,
coercion: CoercionId,
) -> bool {
match key_item.into() {
SemanticIndexKeyItemRef::Field(key_field) => {
key_field == field && coercion == CoercionId::Strict
}
SemanticIndexKeyItemRef::AcceptedExpression(expression) => {
expression.field() == field
&& accepted_expression_supports_lookup_coercion(expression, coercion)
}
}
}
const fn accepted_expression_supports_lookup_coercion(
expression: &SemanticIndexExpression,
coercion: CoercionId,
) -> bool {
match coercion {
CoercionId::TextCasefold => expression.supports_text_casefold_lookup(),
CoercionId::Strict | CoercionId::NumericWiden | CoercionId::CollectionElement => false,
}
}
#[must_use]
pub(in crate::db::query::plan) fn eq_lookup_value_for_key_item<'a>(
key_item: impl Into<SemanticIndexKeyItemRef<'a>>,
field: &str,
value: &Value,
coercion: CoercionId,
literal_compatible: bool,
) -> Option<Value> {
lower_lookup_value_for_key_item(key_item.into(), field, value, coercion, literal_compatible)
}
fn lower_lookup_value_for_key_item(
key_item: SemanticIndexKeyItemRef<'_>,
field: &str,
value: &Value,
coercion: CoercionId,
literal_compatible: bool,
) -> Option<Value> {
match key_item {
SemanticIndexKeyItemRef::Field(key_field) => {
if key_field != field || coercion != CoercionId::Strict || !literal_compatible {
return None;
}
Some(value.clone())
}
SemanticIndexKeyItemRef::AcceptedExpression(expression) => {
if expression.field() != field
|| !accepted_expression_supports_lookup_coercion(expression, coercion)
|| !literal_compatible
{
return None;
}
derive_accepted_index_expression_value(expression, value.clone())
.ok()
.flatten()
}
}
}
fn derive_accepted_index_expression_value(
expression: &SemanticIndexExpression,
source: Value,
) -> Result<Option<Value>, IndexExpressionSourceClass> {
match expression.op() {
PersistedIndexExpressionOp::Lower
| PersistedIndexExpressionOp::Upper
| PersistedIndexExpressionOp::Trim
| PersistedIndexExpressionOp::LowerTrim => {
derive_accepted_text_expression_value(expression.op(), source)
}
PersistedIndexExpressionOp::Date
| PersistedIndexExpressionOp::Year
| PersistedIndexExpressionOp::Month
| PersistedIndexExpressionOp::Day => {
derive_accepted_temporal_expression_value(expression.op(), source)
}
}
}
fn derive_accepted_text_expression_value(
op: PersistedIndexExpressionOp,
source: Value,
) -> Result<Option<Value>, IndexExpressionSourceClass> {
let source = match source {
Value::Null => return Ok(None),
Value::Text(value) => ScalarExprValue::Text(value.into()),
_ => return Err(IndexExpressionSourceClass::Text),
};
derive_non_null_scalar_expression_value(accepted_expression_op(op), source)
.map(scalar_expr_value_into_value)
.map_err(|_| IndexExpressionSourceClass::Text)
.map(Some)
}
fn derive_accepted_temporal_expression_value(
op: PersistedIndexExpressionOp,
source: Value,
) -> Result<Option<Value>, IndexExpressionSourceClass> {
let source = match source {
Value::Null => return Ok(None),
Value::Date(value) => ScalarExprValue::Date(value),
Value::Timestamp(value) => ScalarExprValue::Timestamp(value),
_ => return Err(IndexExpressionSourceClass::DateOrTimestamp),
};
derive_non_null_scalar_expression_value(accepted_expression_op(op), source)
.map(scalar_expr_value_into_value)
.map_err(|_| IndexExpressionSourceClass::DateOrTimestamp)
.map(Some)
}
const fn accepted_expression_op(
op: PersistedIndexExpressionOp,
) -> crate::db::scalar_expr::ScalarIndexExpressionOp {
match op {
PersistedIndexExpressionOp::Lower => crate::db::scalar_expr::ScalarIndexExpressionOp::Lower,
PersistedIndexExpressionOp::Upper => crate::db::scalar_expr::ScalarIndexExpressionOp::Upper,
PersistedIndexExpressionOp::Trim => crate::db::scalar_expr::ScalarIndexExpressionOp::Trim,
PersistedIndexExpressionOp::LowerTrim => {
crate::db::scalar_expr::ScalarIndexExpressionOp::LowerTrim
}
PersistedIndexExpressionOp::Date => crate::db::scalar_expr::ScalarIndexExpressionOp::Date,
PersistedIndexExpressionOp::Year => crate::db::scalar_expr::ScalarIndexExpressionOp::Year,
PersistedIndexExpressionOp::Month => crate::db::scalar_expr::ScalarIndexExpressionOp::Month,
PersistedIndexExpressionOp::Day => crate::db::scalar_expr::ScalarIndexExpressionOp::Day,
}
}
#[must_use]
pub(in crate::db::query::plan) fn starts_with_lookup_value_for_key_item<'a>(
key_item: impl Into<SemanticIndexKeyItemRef<'a>>,
field: &str,
value: &Value,
coercion: CoercionId,
literal_compatible: bool,
) -> Option<String> {
let lowered = lower_lookup_value_for_key_item(
key_item.into(),
field,
value,
coercion,
literal_compatible,
)?;
let Value::Text(prefix) = lowered else {
return None;
};
if prefix.is_empty() {
return None;
}
Some(prefix)
}