use super::Verify;
use crate::schema::app::{self, Field, FieldId, Model, ModelRoot};
use crate::{Error, Result};
fn has_single_field_index(model: &ModelRoot, field: FieldId) -> bool {
model.indices.iter().any(|index| {
!index.primary_key && index.fields.len() == 1 && index.fields[0].field == field
})
}
impl Verify<'_> {
pub(super) fn verify_relations_are_indexed(&self, owner: &Model, field: &Field) -> Result<()> {
use app::FieldTy;
match &field.ty {
FieldTy::BelongsTo(rel) => self.verify_belongs_to_is_indexed(rel),
FieldTy::HasMany(rel) => self.verify_has_many_relation_is_indexed(owner, field, rel),
FieldTy::HasOne(rel) => self.verify_has_one_relation_is_indexed(owner, field, rel),
_ => Ok(()),
}
}
fn verify_belongs_to_is_indexed(&self, _: &app::BelongsTo) -> Result<()> {
Ok(())
}
fn verify_has_many_relation_is_indexed(
&self,
owner: &Model,
field: &Field,
rel: &app::HasMany,
) -> Result<()> {
self.verify_has_relation_is_indexed(owner, field, rel.target(&self.schema.app), rel.pair)
}
fn verify_has_one_relation_is_indexed(
&self,
owner: &Model,
field: &Field,
rel: &app::HasOne,
) -> Result<()> {
self.verify_has_relation_is_indexed(owner, field, rel.target(&self.schema.app), rel.pair)
}
fn verify_has_relation_is_indexed(
&self,
owner: &Model,
field: &Field,
target: &Model,
pair: FieldId,
) -> Result<()> {
let belongs_to = self.schema.app.field(pair).ty.as_belongs_to_unwrap();
let target_root = target.as_root_unwrap();
'outer: for index in &target_root.indices {
assert!(!index.fields.is_empty());
if index.fields.len() < belongs_to.foreign_key.fields.len() {
continue;
}
for (i, fk_field) in belongs_to.foreign_key.fields.iter().enumerate() {
if index.fields[i].field != fk_field.source {
continue 'outer;
}
}
if index.fields.len() == belongs_to.foreign_key.fields.len() {
return Ok(());
}
if index.fields[belongs_to.foreign_key.fields.len()]
.scope
.is_local()
{
return Ok(());
}
}
Err(self.missing_relation_index_error(owner, field, target_root, pair, belongs_to))
}
fn missing_relation_index_error(
&self,
owner: &Model,
field: &Field,
target_root: &ModelRoot,
pair: FieldId,
belongs_to: &app::BelongsTo,
) -> Error {
let owner_name = owner.name();
let target_name = &target_root.name;
let rel_field = &field.name;
let pair_field = &self.schema.app.field(pair).name;
let fk_field_names = belongs_to
.foreign_key
.fields
.iter()
.map(|fk| self.schema.app.field(fk.source).name.to_string())
.collect::<Vec<_>>();
let hint = if fk_field_names.len() == 1 {
format!(
"add `#[index]` to field `{}` on model `{}`",
fk_field_names[0], target_name,
)
} else if belongs_to
.foreign_key
.fields
.iter()
.all(|fk| has_single_field_index(target_root, fk.source))
{
format!(
"each foreign-key field already has its own `#[index]`, but a \
composite foreign key needs a single covering index. Replace \
the per-field `#[index]` annotations with a struct-level \
`#[index({})]` attribute on `{}`",
fk_field_names.join(", "),
target_name,
)
} else {
format!(
"add a struct-level `#[index({})]` attribute to `{}`",
fk_field_names.join(", "),
target_name,
)
};
Error::invalid_schema(format!(
"relation `{owner_name}::{rel_field}` cannot be queried: \
no index on `{target_name}` covers the foreign key \
declared by `{target_name}::{pair_field}` (fields: {}). \
Hint: {hint}.",
fk_field_names.join(", "),
))
}
}