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
//! Rendering of model blocks.

use super::{id, indexes, relation_field, scalar_field};
use crate::introspection::{
    datamodel_calculator::DatamodelCalculatorContext,
    introspection_helpers::{self as helpers, compare_options_none_last},
    introspection_pair::ModelPair,
};
use datamodel_renderer::datamodel as renderer;
use quaint::prelude::SqlFamily;

/// Render all model blocks to the PSL.
pub(super) fn render<'a>(ctx: &'a DatamodelCalculatorContext<'a>, rendered: &mut renderer::Datamodel<'a>) {
    let mut models_with_idx: Vec<(Option<_>, renderer::Model<'a>)> = Vec::with_capacity(ctx.sql_schema.tables_count());

    for model in ctx.model_pairs() {
        models_with_idx.push((model.previous_position(), render_model(model, ctx.sql_family)));
    }

    models_with_idx.sort_by(|(a, _), (b, _)| helpers::compare_options_none_last(*a, *b));

    for (_, render) in models_with_idx.into_iter() {
        rendered.push_model(render);
    }
}

/// Render a single model.
fn render_model(model: ModelPair<'_>, sql_family: SqlFamily) -> renderer::Model<'_> {
    let mut rendered = renderer::Model::new(model.name());

    if let Some(docs) = model.documentation() {
        rendered.documentation(docs);
    }

    if let Some(mapped_name) = model.mapped_name() {
        rendered.map(mapped_name);

        if model.uses_reserved_name() {
            let docs = format!(
                "This model has been renamed to '{}' during introspection, because the original name '{}' is reserved.",
                model.name(),
                mapped_name,
            );

            rendered.documentation(docs);
        }
    }

    if model.new_with_partition() {
        let docs = "This table is a partition table and requires additional setup for migrations. Visit https://pris.ly/d/partition-tables for more info.";

        rendered.documentation(docs);
    }

    if model.new_with_subclass() {
        let docs = "This table has subclasses and requires additional setup for migrations. Visit https://pris.ly/d/table-inheritance for more info.";

        rendered.documentation(docs);
    }

    if model.adds_check_constraints() {
        let docs = "This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info.";

        rendered.documentation(docs);
    }

    if model.adds_exclusion_constraints() {
        let docs = "This table contains exclusion constraints and requires additional setup for migrations. Visit https://pris.ly/d/exclusion-constraints for more info.";

        rendered.documentation(docs);
    }

    if let Some(namespace) = model.namespace() {
        rendered.schema(namespace);
    }

    if model.ignored() {
        rendered.ignore();
    }

    if let Some(id) = model.id() {
        rendered.id(id::render(id));
    }

    if model.scalar_fields().len() == 0 {
        // On postgres this is allowed, on the other dbs, this could be a symptom of missing privileges.
        let docs = if sql_family.is_postgres() {
            "We could not retrieve columns for the underlying table. Either it has none or you are missing rights to see them. Please check your privileges."
        } else {
            "We could not retrieve columns for the underlying table. You probably have no rights to see them. Please check your privileges."
        };

        rendered.documentation(docs);
        rendered.comment_out();
    } else if !model.has_usable_identifier() && !model.ignored_in_psl() {
        let docs = "The underlying table does not contain a valid unique identifier and can therefore currently not be handled by Prisma Client.";

        rendered.documentation(docs);
    }

    if model.adds_a_description() {
        let docs = "This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments";
        rendered.documentation(docs);
    }

    if model.adds_a_row_level_ttl() {
        let docs = "This model is using a row level TTL in the database, and requires an additional setup in migrations. Read more: https://pris.ly/d/row-level-ttl";

        rendered.documentation(docs);
    }

    if model.adds_non_default_deferring() {
        let docs = "This model has constraints using non-default deferring rules and requires additional setup for migrations. Visit https://pris.ly/d/constraint-deferring for more info.";

        rendered.documentation(docs);
    }

    if model.adds_row_level_security() {
        let docs= "This model contains row level security and requires additional setup for migrations. Visit https://pris.ly/d/row-level-security for more info.";

        rendered.documentation(docs);
    }

    if model.adds_non_default_null_position() {
        let docs = "This model contains an index with non-default null sort order and requires additional setup for migrations. Visit https://pris.ly/d/default-index-null-ordering for more info.";

        rendered.documentation(docs);
    }

    if model.expression_indexes().next().is_some() {
        let docs = "This model contains an expression index which requires additional setup for migrations. Visit https://pris.ly/d/expression-indexes for more info.";

        rendered.documentation(docs);
    }

    for field in model.scalar_fields() {
        rendered.push_field(scalar_field::render(field));
    }

    for field in model.relation_fields() {
        rendered.push_field(relation_field::render(field));
    }

    let mut ordered_indexes: Vec<_> = model
        .indexes()
        .map(|idx| (idx.previous_position(), indexes::render(idx)))
        .collect();

    ordered_indexes.sort_by(|(idx, _), (idx_b, _)| compare_options_none_last(*idx, *idx_b));

    for (_, definition) in ordered_indexes {
        rendered.push_index(definition);
    }

    rendered
}