use either::Either;
use super::CompositeTypeFieldWalker;
use crate::{
ast,
types::{IndexAlgorithm, IndexAttribute},
walkers::{ModelWalker, ScalarFieldAttributeWalker, ScalarFieldWalker},
ParserDatabase, ScalarFieldType,
};
#[derive(Copy, Clone)]
pub struct IndexWalker<'db> {
pub(crate) model_id: ast::ModelId,
pub(crate) index: ast::AttributeId,
pub(crate) db: &'db ParserDatabase,
pub(crate) index_attribute: &'db IndexAttribute,
}
impl<'db> IndexWalker<'db> {
pub fn mapped_name(self) -> Option<&'db str> {
self.index_attribute.mapped_name.map(|id| &self.db[id])
}
pub fn attribute_name(self) -> &'static str {
if self.is_unique() && self.is_defined_on_field() {
"@unique"
} else if self.is_unique() {
"@@unique"
} else if self.is_fulltext() {
"@@fulltext"
} else {
"@@index"
}
}
pub fn attribute_id(self) -> ast::AttributeId {
self.index
}
pub fn index_type(self) -> crate::types::IndexType {
self.attribute().r#type
}
pub fn name(self) -> Option<&'db str> {
self.index_attribute.name.map(|id| &self.db[id])
}
pub fn algorithm(self) -> Option<IndexAlgorithm> {
self.attribute().algorithm
}
pub fn ast_attribute(self) -> &'db ast::Attribute {
&self.db.ast[self.index]
}
pub(crate) fn attribute(self) -> &'db IndexAttribute {
self.index_attribute
}
fn fields_array(self) -> &'db [ast::Expression] {
self.ast_attribute()
.arguments
.arguments
.iter()
.find(|arg| match &arg.name {
Some(ident) if ident.name.is_empty() || ident.name == "fields" => true,
None => true,
Some(_) => false,
})
.and_then(|arg| arg.value.as_array())
.unwrap()
.0
}
pub fn all_field_names(self) -> impl Iterator<Item = &'db str> {
self.fields_array()
.iter()
.map(|path| match path {
ast::Expression::ConstantValue(name, _) => name,
ast::Expression::Function(name, _, _) => name,
_ => unreachable!(),
})
.flat_map(|name| name.split('.'))
}
pub fn fields(self) -> impl ExactSizeIterator<Item = IndexFieldWalker<'db>> {
self.index_attribute
.fields
.iter()
.map(move |attributes| match attributes.path.field_in_index() {
Either::Left(ctid) => IndexFieldWalker::new(self.db.walk(ctid)),
Either::Right(id) => IndexFieldWalker::new(self.db.walk(id)),
})
}
pub fn scalar_field_attributes(self) -> impl ExactSizeIterator<Item = ScalarFieldAttributeWalker<'db>> {
self.attribute()
.fields
.iter()
.enumerate()
.map(move |(field_arg_id, _)| ScalarFieldAttributeWalker {
fields: &self.attribute().fields,
db: self.db,
field_arg_id,
})
}
pub fn contains_field(self, field: ScalarFieldWalker<'db>) -> bool {
self.fields().filter_map(|f| f.as_scalar_field()).any(|f| f == field)
}
pub fn contains_exactly_the_fields(
self,
fields: impl ExactSizeIterator<Item = ScalarFieldAttributeWalker<'db>>,
) -> bool {
if self.scalar_field_attributes().len() != fields.len() {
return false;
}
self.scalar_field_attributes().zip(fields).all(|(a, b)| {
let same_attributes = a.sort_order() == b.sort_order() && a.length() == b.length();
let same_path = a.as_path_to_indexed_field() == b.as_path_to_indexed_field();
same_path && same_attributes
})
}
pub fn is_defined_on_field(self) -> bool {
self.index_attribute.source_field.is_some()
}
pub fn is_unique(self) -> bool {
self.index_attribute.is_unique()
}
pub fn is_fulltext(self) -> bool {
self.index_attribute.is_fulltext()
}
pub fn is_normal(self) -> bool {
self.index_attribute.is_normal()
}
pub fn clustered(self) -> Option<bool> {
self.index_attribute.clustered
}
pub fn model(self) -> ModelWalker<'db> {
self.db.walk(self.model_id)
}
pub fn source_field(self) -> Option<ScalarFieldWalker<'db>> {
self.index_attribute.source_field.map(|field_id| self.db.walk(field_id))
}
}
impl<'db> From<ScalarFieldWalker<'db>> for IndexFieldWalker<'db> {
fn from(sf: ScalarFieldWalker<'db>) -> Self {
Self::Scalar(sf)
}
}
impl<'db> From<CompositeTypeFieldWalker<'db>> for IndexFieldWalker<'db> {
fn from(cf: CompositeTypeFieldWalker<'db>) -> Self {
Self::Composite(cf)
}
}
#[derive(Copy, Clone, PartialEq)]
pub enum IndexFieldWalker<'db> {
Scalar(ScalarFieldWalker<'db>),
Composite(CompositeTypeFieldWalker<'db>),
}
impl<'db> IndexFieldWalker<'db> {
pub(super) fn new(inner: impl Into<IndexFieldWalker<'db>>) -> Self {
inner.into()
}
pub fn is_optional(self) -> bool {
match self {
IndexFieldWalker::Scalar(sf) => sf.is_optional(),
IndexFieldWalker::Composite(cf) => cf.arity().is_optional(),
}
}
pub fn is_list(self) -> bool {
match self {
IndexFieldWalker::Scalar(sf) => sf.is_list(),
IndexFieldWalker::Composite(cf) => cf.arity().is_list(),
}
}
pub fn is_unsupported(self) -> bool {
match self {
IndexFieldWalker::Scalar(sf) => sf.is_unsupported(),
IndexFieldWalker::Composite(cf) => cf.r#type().is_unsupported(),
}
}
pub fn field_id(self) -> ast::FieldId {
match self {
IndexFieldWalker::Scalar(sf) => sf.field_id(),
IndexFieldWalker::Composite(cf) => cf.field_id(),
}
}
pub fn name(self) -> &'db str {
match self {
IndexFieldWalker::Scalar(sf) => sf.name(),
IndexFieldWalker::Composite(cf) => cf.name(),
}
}
pub fn database_name(self) -> &'db str {
match self {
IndexFieldWalker::Scalar(sf) => sf.database_name(),
IndexFieldWalker::Composite(cf) => cf.database_name(),
}
}
pub fn scalar_field_type(self) -> ScalarFieldType {
match self {
IndexFieldWalker::Scalar(sf) => sf.scalar_field_type(),
IndexFieldWalker::Composite(cf) => cf.r#type(),
}
}
pub fn as_scalar_field(self) -> Option<ScalarFieldWalker<'db>> {
match self {
IndexFieldWalker::Scalar(sf) => Some(sf),
IndexFieldWalker::Composite(_) => None,
}
}
pub fn as_composite_field(self) -> Option<CompositeTypeFieldWalker<'db>> {
match self {
IndexFieldWalker::Scalar(_) => None,
IndexFieldWalker::Composite(cf) => Some(cf),
}
}
pub fn is_scalar_field(self) -> bool {
matches!(self, IndexFieldWalker::Scalar(_))
}
pub fn is_composite_field(self) -> bool {
matches!(self, IndexFieldWalker::Composite(_))
}
pub fn is_single_pk(self) -> bool {
match self {
IndexFieldWalker::Scalar(sf) => sf.is_single_pk(),
IndexFieldWalker::Composite(_) => false,
}
}
pub fn raw_native_type(self) -> Option<(&'db str, &'db str, &'db [String], ast::Span)> {
match self {
IndexFieldWalker::Scalar(sf) => sf.raw_native_type(),
IndexFieldWalker::Composite(cf) => cf.raw_native_type(),
}
}
pub fn ast_field(self) -> &'db ast::Field {
match self {
IndexFieldWalker::Scalar(sf) => sf.ast_field(),
IndexFieldWalker::Composite(cf) => cf.ast_field(),
}
}
}