use selene_core::DbString;
use crate::{
FilterPredicate, GqlType, LabelExpr, Literal, ValueExpr,
analyze::BindingId,
plan::{BindingDef, BindingElement, IndexKey, IndexKind, IndexTarget, optimize::binding_refs},
};
pub(super) fn single_label(label: &Option<LabelExpr>) -> Option<selene_core::DbString> {
match label {
Some(LabelExpr::Single(label)) => Some(label.clone()),
_ => None,
}
}
pub(super) fn flat_disjunction_singles(
label: &Option<LabelExpr>,
) -> Option<Vec<selene_core::DbString>> {
let Some(LabelExpr::Disjunction(parts)) = label else {
return None;
};
parts
.iter()
.map(|part| match part {
LabelExpr::Single(label) => Some(label.clone()),
_ => None,
})
.collect()
}
pub(super) fn target_for_element(element: BindingElement) -> Option<IndexTarget> {
match element {
BindingElement::Node => Some(IndexTarget::Node),
BindingElement::Edge => Some(IndexTarget::Edge),
BindingElement::Path => None,
}
}
pub(super) fn binding_index_target(
bindings: &[BindingDef],
binding_id: BindingId,
) -> Option<(IndexTarget, selene_core::DbString)> {
let binding = bindings
.iter()
.find(|binding| binding.binding == binding_id)?;
Some((
target_for_element(binding.element)?,
single_label(&binding.label_predicate)?,
))
}
pub(super) fn literal_index_kind(literal: &Literal) -> Option<IndexKind> {
match literal {
Literal::Bool(_, _) => Some(IndexKind::Boolean),
Literal::Integer(_, _) | Literal::RadixInteger(_, _, _) => Some(IndexKind::Integer),
Literal::Decimal(_, _, _) => Some(IndexKind::Decimal),
Literal::Float(_, _, _) => Some(IndexKind::Float),
Literal::String(_, _, _) => Some(IndexKind::String),
Literal::Date(_, _, _) => Some(IndexKind::Date),
Literal::LocalDateTime(_, _, _) => Some(IndexKind::LocalDateTime),
Literal::ZonedDateTime(_, _, _) => Some(IndexKind::ZonedDateTime),
Literal::LocalTime(_, _, _) => Some(IndexKind::LocalTime),
Literal::ZonedTime(_, _, _) => Some(IndexKind::ZonedTime),
Literal::Duration(_, _, _) => Some(IndexKind::Duration),
Literal::Uuid(_, _, _) => Some(IndexKind::Uuid),
Literal::Bytes(_, _) | Literal::Null(_) => None,
}
}
pub(super) fn literal_matches_kind(literal: &Literal, kind: IndexKind) -> bool {
literal_index_kind(literal) == Some(kind)
}
pub(super) fn compatible_value(value: &ValueExpr, kind: IndexKind) -> Option<IndexKey> {
if let Some(literal) = binding_refs::literal(value) {
return literal_matches_kind(literal, kind).then(|| IndexKey::Literal(literal.clone()));
}
let param = binding_refs::parameter(value)?;
if let Some(declared) = param.declared_type
&& !gql_type_compatible_with_index_kind(declared, kind)
{
return None;
}
Some(IndexKey::Parameter {
name: param.name,
declared_type: param.declared_type.cloned(),
span: param.span,
})
}
pub(super) fn compatible_list_parameter(value: &ValueExpr, kind: IndexKind) -> Option<IndexKey> {
let param = binding_refs::parameter(value)?;
let declared = param.declared_type?;
let inner = match declared {
GqlType::List(inner)
| GqlType::BoundedList {
element_type: inner,
..
} => inner,
_ => return None,
};
if !gql_type_compatible_with_index_kind(inner, kind) {
return None;
}
Some(IndexKey::ParameterList {
name: param.name,
declared_type: declared.clone(),
span: param.span,
})
}
#[derive(Clone)]
pub(super) struct EqualityCandidate<'a> {
pub(super) index: usize,
pub(super) key: DbString,
pub(super) value: &'a ValueExpr,
}
pub(super) fn equality_candidates<'a>(
predicates: &'a [FilterPredicate],
bindings: &'a [BindingDef],
) -> Vec<EqualityCandidate<'a>> {
let mut candidates: Vec<EqualityCandidate<'a>> = Vec::new();
for (index, pred) in predicates.iter().enumerate() {
let Some(matched) = binding_refs::match_property_predicate(pred, bindings) else {
continue;
};
let binding_id = matched.binding;
let Some(binding_def) = bindings
.iter()
.find(|binding| binding.binding == binding_id)
else {
continue;
};
if binding_def.element != BindingElement::Node {
continue;
}
if candidates
.iter()
.any(|candidate| candidate.key == matched.key)
{
continue;
}
let binding_refs::PropertyPredicateShape::Equality(value) = matched.shape else {
continue;
};
if binding_refs::literal(value).is_none() && binding_refs::parameter(value).is_none() {
continue;
}
candidates.push(EqualityCandidate {
index,
key: matched.key,
value,
});
}
candidates
}
pub(super) fn gql_type_compatible_with_index_kind(ty: &GqlType, kind: IndexKind) -> bool {
match kind {
IndexKind::Boolean => matches!(ty, GqlType::Boolean),
IndexKind::Integer => matches!(
ty,
GqlType::Integer
| GqlType::Int8
| GqlType::Int16
| GqlType::Int32
| GqlType::Int64
| GqlType::SmallInt
| GqlType::BigInt
),
IndexKind::UnsignedInteger => {
matches!(
ty,
GqlType::Uint8
| GqlType::Uint16
| GqlType::Uint32
| GqlType::Uint64
| GqlType::USmallInt
| GqlType::Uint
| GqlType::UBigInt
)
}
IndexKind::Integer128 => matches!(ty, GqlType::Int128),
IndexKind::UnsignedInteger128 => matches!(ty, GqlType::Uint128),
IndexKind::Decimal => matches!(ty, GqlType::Decimal | GqlType::DecimalExact(_)),
IndexKind::Float32 => matches!(ty, GqlType::Float32 | GqlType::Real),
IndexKind::Float => matches!(ty, GqlType::Float64 | GqlType::Double),
IndexKind::String => matches!(ty, GqlType::String | GqlType::CharacterString(_)),
IndexKind::Date => matches!(ty, GqlType::Date),
IndexKind::LocalDateTime => matches!(ty, GqlType::LocalDateTime),
IndexKind::ZonedDateTime => matches!(ty, GqlType::ZonedDateTime),
IndexKind::LocalTime => matches!(ty, GqlType::LocalTime),
IndexKind::ZonedTime => matches!(ty, GqlType::ZonedTime),
IndexKind::Duration => {
matches!(
ty,
GqlType::Duration | GqlType::DurationYearToMonth | GqlType::DurationDayToSecond
)
}
IndexKind::Uuid => matches!(ty, GqlType::Uuid),
}
}