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
use crate::{
    ast::{self, WithName, WithSpan},
    coerce,
    context::Context,
    types::ModelAttributes,
    DatamodelError, ScalarFieldId, StringId,
};

pub(super) fn model(model_attributes: &mut ModelAttributes, ctx: &mut Context<'_>) {
    let mapped_name = match visit_map_attribute(ctx) {
        Some(name) => name,
        None => return,
    };

    model_attributes.mapped_name = Some(mapped_name);
}

pub(super) fn scalar_field(
    sfid: ScalarFieldId,
    ast_model: &ast::Model,
    ast_field: &ast::Field,
    model_id: ast::ModelId,
    field_id: ast::FieldId,
    ctx: &mut Context<'_>,
) {
    let mapped_name = match visit_map_attribute(ctx) {
        Some(name) => name,
        None => return,
    };

    ctx.types[sfid].mapped_name = Some(mapped_name);

    if ctx
        .mapped_model_scalar_field_names
        .insert((model_id, mapped_name), field_id)
        .is_some()
    {
        ctx.push_error(DatamodelError::new_duplicate_field_error(
            ast_model.name(),
            ast_field.name(),
            if ast_model.is_view() { "view" } else { "model" },
            ast_field.span(),
        ));
    }

    if let Some(dup_field_id) = ctx.names.model_fields.get(&(model_id, mapped_name)) {
        match ctx
            .types
            .range_model_scalar_fields(model_id)
            // Do not compare field to itself
            .filter(|(_, sf)| sf.field_id != field_id)
            // Find the field with the given mapped name.
            .find(|(_, sf)| sf.field_id == *dup_field_id)
            .map(|(_, sf)| sf.mapped_name)
        {
            // @map only conflicts with _scalar_ fields
            None => return,
            Some(Some(sf_mapped_name)) if sf_mapped_name != mapped_name => return,
            Some(_) => {}
        }

        ctx.push_error(DatamodelError::new_duplicate_field_error(
            ast_model.name(),
            ast_field.name(),
            if ast_model.is_view() { "view" } else { "model" },
            ast_field.span(),
        ))
    }
}

pub(super) fn composite_type_field(
    ct: &ast::CompositeType,
    ast_field: &ast::Field,
    ctid: ast::CompositeTypeId,
    field_id: ast::FieldId,
    ctx: &mut Context<'_>,
) {
    let mapped_name_id = match visit_map_attribute(ctx) {
        Some(name) => name,
        None => return,
    };

    {
        let field = ctx.types.composite_type_fields.get_mut(&(ctid, field_id)).unwrap();
        field.mapped_name = Some(mapped_name_id);
    }

    if ctx
        .mapped_composite_type_names
        .insert((ctid, mapped_name_id), field_id)
        .is_some()
    {
        ctx.push_error(DatamodelError::new_composite_type_duplicate_field_error(
            ct.name(),
            &ctx[mapped_name_id],
            ast_field.span(),
        ));
    }

    if let Some(f) = ctx.names.composite_type_fields.get(&(ctid, mapped_name_id)) {
        let other_field = &ctx.types.composite_type_fields[&(ctid, *f)];

        // We check mapped name collisions above. In this part, if the other
        // field has a mapped name, they cannot collide.
        if other_field.mapped_name.is_some() {
            return;
        }

        ctx.push_error(DatamodelError::new_composite_type_duplicate_field_error(
            ct.name(),
            ast_field.name(),
            ast_field.span(),
        ));
    }
}

pub(super) fn visit_map_attribute(ctx: &mut Context<'_>) -> Option<StringId> {
    match ctx
        .visit_default_arg("name")
        .map(|value| coerce::string(value, ctx.diagnostics))
    {
        Ok(Some(name)) => return Some(ctx.interner.intern(name)),
        Err(err) => ctx.push_error(err), // not flattened for error handing legacy reasons
        Ok(None) => (),
    };

    None
}