use crate::{
db::{
query::plan::{
OrderSpec, OrderTerm,
expr::{ExprType, infer_expr_type},
validate::{OrderPlanError, PlanError},
},
schema::SchemaInfo,
},
model::entity::EntityModel,
};
pub(crate) fn validate_order(schema: &SchemaInfo, order: &OrderSpec) -> Result<(), PlanError> {
for term in &order.fields {
validate_order_term(schema, term)?;
}
Ok(())
}
fn validate_order_term(schema: &SchemaInfo, term: &OrderTerm) -> Result<(), PlanError> {
if let Some(field) = term.direct_field() {
let Some(field_type) = schema.field(field) else {
return Err(PlanError::from(OrderPlanError::UnknownField {
field: field.to_owned(),
}));
};
return field_type
.is_orderable()
.then_some(())
.ok_or_else(|| PlanError::from(OrderPlanError::unorderable_field(field)));
}
validate_expression_order_term(schema, term)
}
fn validate_expression_order_term(schema: &SchemaInfo, term: &OrderTerm) -> Result<(), PlanError> {
let inferred = infer_expr_type(term.expr(), schema)?;
if !matches!(
inferred,
ExprType::Bool | ExprType::Text | ExprType::Numeric(_)
) {
return Err(PlanError::from(OrderPlanError::unorderable_field(
term.rendered_label(),
)));
}
Ok(())
}
pub(crate) fn validate_no_duplicate_non_pk_order_fields(
model: &EntityModel,
order: &OrderSpec,
) -> Result<(), PlanError> {
let mut seen = Vec::with_capacity(order.fields.len());
let pk_field = model.primary_key.name;
for term in &order.fields {
let field = term
.direct_field()
.map_or_else(|| term.rendered_label(), str::to_owned);
let non_pk_field = field != pk_field;
if !non_pk_field {
continue;
}
if seen.iter().any(|seen_field| seen_field == &field) {
return Err(PlanError::from(OrderPlanError::duplicate_order_field(
field,
)));
}
seen.push(field);
}
Ok(())
}
pub(crate) fn validate_primary_key_tie_break(
model: &EntityModel,
order: &OrderSpec,
) -> Result<(), PlanError> {
order.fields.is_empty().then_some(()).map_or_else(
|| {
let pk_field = model.primary_key.name;
order
.has_exact_primary_key_tie_break(pk_field)
.then_some(())
.ok_or_else(|| {
PlanError::from(OrderPlanError::missing_primary_key_tie_break(pk_field))
})
},
|()| Ok(()),
)
}