1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
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};

/// A `model` declaration in the Prisma schema.
pub type ModelWalker<'db> = super::Walker<'db, ast::ModelId>;

impl<'db> ModelWalker<'db> {
    /// The name of the model.
    pub fn name(self) -> &'db str {
        self.ast_model().name()
    }

    /// Traverse the fields of the models in the order they were defined.
    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)))
    }

    /// Whether MySQL would consider the field indexed for autoincrement purposes.
    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()
    }

    /// Whether the field is the whole primary key. Will match `@id` and `@@id([fieldName])`.
    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()
    }

    /// Is the field part of a compound primary key.
    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()
    }

    /// The ID of the model in the db
    pub fn model_id(self) -> ast::ModelId {
        self.id
    }

    /// The AST node.
    pub fn ast_model(self) -> &'db ast::Model {
        &self.db.ast[self.id]
    }

    /// The parsed attributes.
    #[track_caller]
    pub(crate) fn attributes(self) -> &'db ModelAttributes {
        &self.db.types.model_attributes[&self.id]
    }

    /// Model has the @@ignore attribute.
    pub fn is_ignored(self) -> bool {
        self.attributes().is_ignored
    }

    /// The name of the database table the model points to.
    #[allow(clippy::unnecessary_lazy_evaluations)] // respectfully disagree
    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())
    }

    /// Used in validation. True only if the model has a single field id.
    pub fn has_single_id_field(self) -> bool {
        matches!(&self.attributes().primary_key, Some(pk) if pk.fields.len() == 1)
    }

    /// The name in the @@map attribute.
    pub fn mapped_name(self) -> Option<&'db str> {
        self.attributes().mapped_name.map(|id| &self.db[id])
    }

    /// The primary key of the model, if defined.
    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,
        })
    }

    /// Iterate all the scalar fields in a given model in the order they were defined.
    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))
    }

    /// All unique criterias of the model; consisting of the primary key and
    /// unique indexes, if set.
    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)
    }

    /// All _required_ unique criterias of the model; consisting of the primary key and
    /// unique indexes, if set.
    pub fn required_unique_criterias(self) -> impl Iterator<Item = UniqueCriteriaWalker<'db>> {
        self.unique_criterias()
            .filter(|walker| !walker.fields().any(|field| field.is_optional()))
    }

    /// Iterate all the indexes in the model in the order they were
    /// defined.
    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,
            })
    }

    /// All (concrete) relation fields of the model.
    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))
    }

    /// All relations that start from this model.
    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,
            })
    }

    /// All relations that reference this model.
    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,
            })
    }

    /// 1:n and 1:1 relations that start from this model.
    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,
        })
    }

    /// 1:n and 1:1 relations, starting from this model and having both sides defined.
    pub fn complete_inline_relations_from(self) -> impl Iterator<Item = CompleteInlineRelationWalker<'db>> {
        self.inline_relations_from()
            .filter_map(|relation| relation.as_complete())
    }

    /// How fields and arguments are indented in the model.
    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()
    }

    /// What kind of newlines the model uses.
    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,
        }
    }

    /// The name of the schema the model belongs to.
    ///
    /// ```ignore
    /// @@schema("public")
    ///          ^^^^^^^^
    /// ```
    pub fn schema(self) -> Option<(&'db str, ast::Span)> {
        self.attributes().schema.map(|(id, span)| (&self.db[id], span))
    }

    /// The name of the schema the model belongs to.
    ///
    /// ```ignore
    /// @@schema("public")
    ///          ^^^^^^^^
    /// ```
    pub fn schema_name(self) -> Option<&'db str> {
        self.schema().map(|(name, _)| name)
    }
}