mod primary_key;
mod unique_criteria;
pub use primary_key::*;
pub(crate) use unique_criteria::*;
use super::{
CompleteInlineRelationWalker, FieldWalker, IndexWalker, InlineRelationWalker, RelationFieldWalker, RelationWalker,
ScalarFieldWalker,
};
use crate::{
ast::{self, WithName},
types::ModelAttributes,
};
use schema_ast::ast::{IndentationType, NewlineType, WithSpan};
pub type ModelWalker<'db> = super::Walker<'db, ast::ModelId>;
impl<'db> ModelWalker<'db> {
pub fn name(self) -> &'db str {
self.ast_model().name()
}
pub fn fields(self) -> impl ExactSizeIterator<Item = FieldWalker<'db>> + Clone {
self.ast_model()
.iter_fields()
.map(move |(field_id, _)| self.walk((self.id, field_id)))
}
pub fn field_is_indexed_for_autoincrement(self, field_id: ast::FieldId) -> bool {
self.indexes()
.any(|idx| idx.fields().next().map(|f| f.field_id()) == Some(field_id))
|| self
.primary_key()
.filter(|pk| pk.fields().next().map(|f| f.field_id()) == Some(field_id))
.is_some()
}
pub fn field_is_single_pk(self, field: ast::FieldId) -> bool {
self.primary_key()
.filter(|pk| pk.fields().map(|f| f.field_id()).collect::<Vec<_>>() == [field])
.is_some()
}
pub fn field_is_part_of_a_compound_pk(self, field: ast::FieldId) -> bool {
self.primary_key()
.filter(|pk| {
let exists = pk.fields().map(|f| f.field_id()).any(|f| f == field);
exists && pk.fields().len() > 1
})
.is_some()
}
pub fn model_id(self) -> ast::ModelId {
self.id
}
pub fn ast_model(self) -> &'db ast::Model {
&self.db.ast[self.id]
}
#[track_caller]
pub(crate) fn attributes(self) -> &'db ModelAttributes {
&self.db.types.model_attributes[&self.id]
}
pub fn is_ignored(self) -> bool {
self.attributes().is_ignored
}
#[allow(clippy::unnecessary_lazy_evaluations)] pub fn database_name(self) -> &'db str {
self.attributes()
.mapped_name
.map(|id| &self.db[id])
.unwrap_or_else(|| self.db.ast[self.id].name())
}
pub fn has_single_id_field(self) -> bool {
matches!(&self.attributes().primary_key, Some(pk) if pk.fields.len() == 1)
}
pub fn mapped_name(self) -> Option<&'db str> {
self.attributes().mapped_name.map(|id| &self.db[id])
}
pub fn primary_key(self) -> Option<PrimaryKeyWalker<'db>> {
self.attributes().primary_key.as_ref().map(|pk| PrimaryKeyWalker {
model_id: self.id,
attribute: pk,
db: self.db,
})
}
pub fn scalar_fields(self) -> impl Iterator<Item = ScalarFieldWalker<'db>> + Clone {
self.db
.types
.range_model_scalar_fields(self.id)
.map(move |(id, _)| self.walk(id))
}
pub fn unique_criterias(self) -> impl Iterator<Item = UniqueCriteriaWalker<'db>> {
let db = self.db;
let from_pk = self
.attributes()
.primary_key
.iter()
.map(move |pk| UniqueCriteriaWalker { fields: &pk.fields, db });
let from_indices = self
.indexes()
.filter(|walker| walker.attribute().is_unique())
.map(move |walker| UniqueCriteriaWalker {
fields: &walker.attribute().fields,
db,
});
from_pk.chain(from_indices)
}
pub fn required_unique_criterias(self) -> impl Iterator<Item = UniqueCriteriaWalker<'db>> {
self.unique_criterias()
.filter(|walker| !walker.fields().any(|field| field.is_optional()))
}
pub fn indexes(self) -> impl Iterator<Item = IndexWalker<'db>> {
let model_id = self.id;
let db = self.db;
self.attributes()
.ast_indexes
.iter()
.map(move |(index, index_attribute)| IndexWalker {
model_id,
index: *index,
db,
index_attribute,
})
}
pub fn relation_fields(self) -> impl Iterator<Item = RelationFieldWalker<'db>> + Clone + 'db {
let model_id = self.id;
self.db
.types
.range_model_relation_fields(model_id)
.map(move |(id, _)| self.walk(id))
}
pub fn relations_from(self) -> impl Iterator<Item = RelationWalker<'db>> {
self.db
.relations
.from_model(self.id)
.map(move |relation_id| RelationWalker {
id: relation_id,
db: self.db,
})
}
pub fn relations_to(self) -> impl Iterator<Item = RelationWalker<'db>> {
self.db
.relations
.to_model(self.id)
.map(move |relation_id| RelationWalker {
id: relation_id,
db: self.db,
})
}
pub fn inline_relations_from(self) -> impl Iterator<Item = InlineRelationWalker<'db>> {
self.relations_from().filter_map(|relation| match relation.refine() {
super::RefinedRelationWalker::Inline(relation) => Some(relation),
super::RefinedRelationWalker::ImplicitManyToMany(_) => None,
super::RefinedRelationWalker::TwoWayEmbeddedManyToMany(_) => None,
})
}
pub fn complete_inline_relations_from(self) -> impl Iterator<Item = CompleteInlineRelationWalker<'db>> {
self.inline_relations_from()
.filter_map(|relation| relation.as_complete())
}
pub fn indentation(self) -> IndentationType {
let field = match self.scalar_fields().last() {
Some(field) => field,
None => return IndentationType::default(),
};
let src = self.db.source();
let start = field.ast_field().span().start;
let mut spaces = 0;
for i in (0..start).rev() {
if src.is_char_boundary(i) {
match src[i..].chars().next() {
Some('\t') => return IndentationType::Tabs,
Some(' ') => spaces += 1,
_ => return IndentationType::Spaces(spaces),
}
}
}
IndentationType::default()
}
pub fn newline(self) -> NewlineType {
let field = match self.scalar_fields().last() {
Some(field) => field,
None => return NewlineType::default(),
};
let src = self.db.source();
let start = field.ast_field().span().end - 2;
match src.chars().nth(start) {
Some('\r') => NewlineType::Windows,
_ => NewlineType::Unix,
}
}
pub fn schema(self) -> Option<(&'db str, ast::Span)> {
self.attributes().schema.map(|(id, span)| (&self.db[id], span))
}
pub fn schema_name(self) -> Option<&'db str> {
self.schema().map(|(name, _)| name)
}
}