asn1rs_model/model/sql/
mod.rs

1use crate::gen::RustCodeGenerator;
2use crate::model::rust::{DataEnum, DataVariant, EncodingOrdering};
3use crate::model::rust::{Field, PlainEnum};
4use crate::model::Rust;
5use crate::model::RustType;
6use crate::model::{Charset, Model};
7use crate::model::{Definition, Size};
8use crate::model::{Range, Target};
9use std::collections::HashMap;
10use std::convert::Infallible;
11
12const FOREIGN_KEY_DEFAULT_COLUMN: &str = "id";
13const TUPLE_LIST_ENTRY_PARENT_COLUMN: &str = "list";
14const TUPLE_LIST_ENTRY_VALUE_COLUMN: &str = "value";
15
16/// Default seems to be 63, dont exceed it
17/// https://github.com/kellerkindt/asn1rs/issues/75#issuecomment-1110804868
18const TYPENAME_LENGTH_LIMIT_PSQL: usize = 63;
19
20#[derive(Debug, Clone, PartialEq, PartialOrd)]
21#[allow(clippy::module_name_repetitions)]
22pub enum SqlType {
23    SmallInt, // 2byte
24    Integer,  // 4byte
25    BigInt,   // 8byte
26    Serial,   // 4byte
27    Boolean,
28    Text,
29    Array(Box<SqlType>),
30    NotNull(Box<SqlType>),
31    ByteArray,
32    NullByteArray,
33    BitsReprByByteArrayAndBitsLen,
34    References(String, String, Option<Action>, Option<Action>),
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
38pub enum Action {
39    Cascade,
40    Restrict,
41}
42
43impl ToString for Action {
44    fn to_string(&self) -> String {
45        match self {
46            Action::Cascade => "CASCADE",
47            Action::Restrict => "RESTRICT",
48        }
49        .into()
50    }
51}
52
53impl SqlType {
54    pub fn as_nullable(&self) -> &Self {
55        match self {
56            SqlType::NotNull(inner) => inner,
57            other => other,
58        }
59    }
60
61    pub fn nullable(self) -> Self {
62        match self {
63            SqlType::NotNull(inner) => *inner,
64            other => other,
65        }
66    }
67
68    pub fn not_null(self) -> Self {
69        SqlType::NotNull(Box::new(self))
70    }
71
72    pub fn to_rust(&self) -> RustType {
73        #[allow(clippy::match_same_arms)] // to have the same order as the original enum
74        RustType::Option(Box::new(match self {
75            SqlType::SmallInt => RustType::I16(Range::inclusive(0, i16::MAX)),
76            SqlType::Integer => RustType::I32(Range::inclusive(0, i32::MAX)),
77            SqlType::BigInt => RustType::I64(Range::inclusive(0, i64::MAX)),
78            SqlType::Serial => RustType::I32(Range::inclusive(0, i32::MAX)),
79            SqlType::Boolean => RustType::Bool,
80            SqlType::Text => RustType::String(Size::Any, Charset::Utf8),
81            SqlType::Array(inner) => {
82                RustType::Vec(Box::new(inner.to_rust()), Size::Any, EncodingOrdering::Keep)
83            }
84            SqlType::NotNull(inner) => return inner.to_rust().no_option(),
85            SqlType::ByteArray => RustType::VecU8(Size::Any),
86            SqlType::NullByteArray => return RustType::Null,
87            SqlType::BitsReprByByteArrayAndBitsLen => RustType::BitVec(Size::Any),
88            SqlType::References(name, _, _, _) => RustType::Complex(name.clone(), None),
89        }))
90    }
91}
92
93impl ToString for SqlType {
94    fn to_string(&self) -> String {
95        match self {
96            SqlType::SmallInt => "SMALLINT".into(),
97            SqlType::Integer => "INTEGER".into(),
98            SqlType::BigInt => "BIGINT".into(),
99            SqlType::Serial => "SERIAL".into(),
100            SqlType::Boolean => "BOOLEAN".into(),
101            SqlType::Text => "TEXT".into(),
102            SqlType::Array(inner) => format!("{}[]", inner.to_string()),
103            SqlType::NotNull(inner) => format!("{} NOT NULL", inner.to_string()),
104            SqlType::ByteArray
105            | SqlType::NullByteArray
106            | SqlType::BitsReprByByteArrayAndBitsLen => "BYTEA".into(),
107            SqlType::References(table, column, on_delete, on_update) => format!(
108                "INTEGER REFERENCES {}({}){}{}",
109                Model::<Sql>::sql_definition_name(table),
110                column,
111                if let Some(cascade) = on_delete {
112                    format!(" ON DELETE {}", cascade.to_string())
113                } else {
114                    "".into()
115                },
116                if let Some(cascade) = on_update {
117                    format!(" ON UPDATE {}", cascade.to_string())
118                } else {
119                    "".into()
120                },
121            ),
122        }
123    }
124}
125
126#[derive(Debug, Clone, PartialEq)]
127pub struct Column {
128    pub name: String,
129    pub sql: SqlType,
130    pub primary_key: bool,
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
134pub enum Constraint {
135    CombinedPrimaryKey(Vec<String>),
136    OneNotNull(Vec<String>),
137}
138
139#[derive(Debug, Clone, PartialEq)]
140pub enum Sql {
141    Table(Vec<Column>, Vec<Constraint>),
142    Enum(Vec<String>),
143    Index(String, Vec<String>),
144    /// Table being affected to ->
145    AbandonChildrenFunction(String, Vec<(String, String, String)>),
146    SilentlyPreventAnyDelete(String),
147}
148
149impl Target for Sql {
150    type DefinitionType = Self;
151    type ValueReferenceType = Infallible;
152}
153
154impl Model<Sql> {
155    pub fn convert_rust_to_sql(rust_model: &Model<Rust>) -> Model<Sql> {
156        let mut model = Model {
157            name: rust_model.name.clone(),
158            oid: rust_model.oid.clone(),
159            imports: Default::default(), // ignored in SQL
160            definitions: Vec::with_capacity(rust_model.definitions.len()),
161            value_references: Vec::default(),
162        };
163        for Definition(name, rust) in &rust_model.definitions {
164            let name = Self::sql_definition_name(name);
165            Self::definition_to_sql(&name, rust, &mut model.definitions);
166        }
167        model.fix_table_declaration_occurrence();
168        model
169    }
170
171    fn fix_table_declaration_occurrence(&mut self) {
172        let mut depth = HashMap::<String, usize>::default();
173        for i in 0..self.definitions.len() {
174            self.walk_graph(i, &mut depth, 0);
175        }
176        self.definitions
177            .sort_by_key(|key| usize::MAX - depth.get(key.name()).unwrap_or(&0_usize));
178    }
179
180    fn walk_graph(&self, current: usize, depths: &mut HashMap<String, usize>, depth: usize) {
181        let name = self.definitions[current].name();
182        let depth = if let Some(d) = depths.get(name) {
183            if depth > *d {
184                depths.insert(name.to_string(), depth);
185                depth
186            } else {
187                *d
188            }
189        } else {
190            depths.insert(name.to_string(), depth);
191            depth
192        };
193        let mut walk_from_name = |name: &str| {
194            for (index, def) in self.definitions.iter().enumerate() {
195                if def.name().eq(name) {
196                    self.walk_graph(index, depths, depth + 1);
197                    break;
198                }
199            }
200        };
201        match self.definitions[current].value() {
202            Sql::Table(columns, _constraints) => {
203                for column in columns {
204                    if let SqlType::References(other, _, _, _) = column.sql.as_nullable() {
205                        walk_from_name(other.as_str());
206                    }
207                }
208            }
209            Sql::Enum(_) => {}
210            Sql::Index(name, _)
211            | Sql::AbandonChildrenFunction(name, _)
212            | Sql::SilentlyPreventAnyDelete(name) => {
213                walk_from_name(name.as_str());
214            }
215        }
216    }
217
218    fn definition_to_sql(name: &str, rust: &Rust, definitions: &mut Vec<Definition<Sql>>) {
219        match rust {
220            Rust::Struct {
221                fields,
222                tag: _,
223                extension_after: _,
224                ordering: _,
225            } => Self::rust_struct_to_sql_table(name, fields, definitions),
226            Rust::Enum(rust_enum) => Self::rust_enum_to_sql_enum(name, rust_enum, definitions),
227            Rust::DataEnum(enumeration) => {
228                Self::rust_data_enum_to_sql_table(name, enumeration, definitions)
229            }
230            Rust::TupleStruct { r#type: rust, .. } => {
231                Self::rust_tuple_struct_to_sql_table(name, rust, definitions)
232            }
233        }
234    }
235
236    pub fn rust_struct_to_sql_table(
237        name: &str,
238        fields: &[Field],
239        definitions: &mut Vec<Definition<Sql>>,
240    ) {
241        let mut deferred = Vec::default();
242        let mut columns = Vec::with_capacity(fields.len() + 1);
243        columns.push(Column {
244            name: FOREIGN_KEY_DEFAULT_COLUMN.into(),
245            sql: SqlType::Serial,
246            primary_key: true,
247        });
248        for field in fields {
249            if field.r#type().is_vec() {
250                let list_entry_name = Self::struct_list_entry_table_name(name, field.name());
251                let value_sql_type = field.r#type().clone().as_inner_type().to_sql();
252                Self::add_list_table(name, &mut deferred, &list_entry_name, &value_sql_type);
253            } else {
254                columns.push(Column {
255                    name: Self::sql_column_name(field.name()),
256                    sql: field.r#type().to_sql(),
257                    primary_key: false,
258                });
259            }
260        }
261        definitions.push(Definition(
262            name.into(),
263            Sql::Table(columns, Default::default()),
264        ));
265
266        Self::append_index_and_abandon_function(
267            name,
268            fields.iter().map(Field::fallback_representation),
269            definitions,
270        );
271        deferred.into_iter().for_each(|e| definitions.push(e));
272    }
273
274    pub fn rust_data_enum_to_sql_table(
275        name: &str,
276        enumeration: &DataEnum,
277        definitions: &mut Vec<Definition<Sql>>,
278    ) {
279        let mut columns = Vec::with_capacity(enumeration.len() + 1);
280        // TODO
281        if !enumeration
282            .variants()
283            .map(|variant| FOREIGN_KEY_DEFAULT_COLUMN.eq_ignore_ascii_case(variant.name()))
284            .any(|found| found)
285        {
286            columns.push(Column {
287                name: FOREIGN_KEY_DEFAULT_COLUMN.into(),
288                sql: SqlType::Serial,
289                primary_key: true,
290            });
291        }
292        for variant in enumeration.variants() {
293            columns.push(Column {
294                name: Self::sql_column_name(variant.name()),
295                sql: variant.r#type().to_sql().nullable(),
296                primary_key: false,
297            });
298        }
299        definitions.push(Definition(
300            name.into(),
301            Sql::Table(
302                columns,
303                vec![Constraint::OneNotNull(
304                    enumeration
305                        .variants()
306                        .map(|variant| RustCodeGenerator::rust_module_name(variant.name()))
307                        .collect::<Vec<String>>(),
308                )],
309            ),
310        ));
311
312        Self::append_index_and_abandon_function(
313            name,
314            enumeration
315                .variants()
316                .map(DataVariant::fallback_representation),
317            definitions,
318        );
319    }
320
321    fn add_index_if_applicable(
322        table: &str,
323        column: &str,
324        rust: &RustType,
325        definitions: &mut Vec<Definition<Sql>>,
326    ) {
327        if let SqlType::References(..) = rust.to_sql().nullable() {
328            definitions.push(Self::index_def(table, column));
329        }
330    }
331
332    fn index_def(table: &str, column: &str) -> Definition<Sql> {
333        Definition(
334            Self::sql_definition_name(&format!("{}_Index_{}", table, column)),
335            Sql::Index(table.into(), vec![column.into()]),
336        )
337    }
338
339    pub fn rust_enum_to_sql_enum(
340        name: &str,
341        enumeration: &PlainEnum,
342        definitions: &mut Vec<Definition<Sql>>,
343    ) {
344        let variants = enumeration.variants().map(String::clone).collect();
345        definitions.push(Definition(name.into(), Sql::Enum(variants)));
346        Self::add_silently_prevent_any_delete(name, definitions);
347    }
348
349    pub fn rust_tuple_struct_to_sql_table(
350        name: &str,
351        rust_inner: &RustType,
352        definitions: &mut Vec<Definition<Sql>>,
353    ) {
354        {
355            definitions.push(Definition(
356                name.into(),
357                Sql::Table(
358                    vec![Column {
359                        name: FOREIGN_KEY_DEFAULT_COLUMN.into(),
360                        sql: SqlType::Serial,
361                        primary_key: true,
362                    }],
363                    Default::default(),
364                ),
365            ));
366        }
367        {
368            let list_entry_name = Self::sql_definition_name(&format!("{}ListEntry", name));
369            let value_sql_type = rust_inner.clone().as_inner_type().to_sql();
370            Self::add_list_table(name, definitions, &list_entry_name, &value_sql_type);
371        }
372    }
373
374    fn add_list_table(
375        name: &str,
376        definitions: &mut Vec<Definition<Sql>>,
377        list_entry_name: &str,
378        value_sql_type: &SqlType,
379    ) {
380        definitions.push(Definition(
381            list_entry_name.to_string(),
382            Sql::Table(
383                vec![
384                    Column {
385                        name: TUPLE_LIST_ENTRY_PARENT_COLUMN.into(),
386                        sql: SqlType::NotNull(Box::new(SqlType::References(
387                            name.into(),
388                            FOREIGN_KEY_DEFAULT_COLUMN.into(),
389                            Some(Action::Cascade),
390                            Some(Action::Cascade),
391                        ))),
392                        primary_key: false,
393                    },
394                    Column {
395                        name: TUPLE_LIST_ENTRY_VALUE_COLUMN.into(),
396                        sql: value_sql_type.clone(),
397                        primary_key: false,
398                    },
399                ],
400                vec![Constraint::CombinedPrimaryKey(vec![
401                    TUPLE_LIST_ENTRY_PARENT_COLUMN.into(),
402                    TUPLE_LIST_ENTRY_VALUE_COLUMN.into(),
403                ])],
404            ),
405        ));
406        definitions.push(Self::index_def(
407            list_entry_name,
408            TUPLE_LIST_ENTRY_PARENT_COLUMN,
409        ));
410        definitions.push(Self::index_def(
411            list_entry_name,
412            TUPLE_LIST_ENTRY_VALUE_COLUMN,
413        ));
414        if let SqlType::References(other_table, other_column, ..) =
415            value_sql_type.clone().nullable()
416        {
417            Self::add_abandon_children(
418                list_entry_name,
419                vec![(
420                    TUPLE_LIST_ENTRY_VALUE_COLUMN.into(),
421                    other_table,
422                    other_column,
423                )],
424                definitions,
425            );
426        }
427    }
428
429    pub fn sql_column_name(name: &str) -> String {
430        if FOREIGN_KEY_DEFAULT_COLUMN.eq_ignore_ascii_case(name.trim()) {
431            let mut string = RustCodeGenerator::rust_module_name(name);
432            string.push('_');
433            string
434        } else {
435            RustCodeGenerator::rust_module_name(name)
436        }
437    }
438
439    pub fn sql_definition_name(name: &str) -> String {
440        if name.len() > TYPENAME_LENGTH_LIMIT_PSQL {
441            // carve out lower case segments at the beginning until it fits into the limit
442            let mut result = String::with_capacity(TYPENAME_LENGTH_LIMIT_PSQL);
443            for (index, character) in name.chars().enumerate() {
444                if character.is_ascii_lowercase() {
445                    // skip
446                } else {
447                    result.push(character);
448                    // check if the rest could just be copied over
449                    let remainig = (name.len() - index).saturating_sub(1);
450                    if remainig + result.len() <= TYPENAME_LENGTH_LIMIT_PSQL {
451                        result.push_str(&name[index + 1..]);
452                        break;
453                    } else if result.len() >= TYPENAME_LENGTH_LIMIT_PSQL {
454                        break;
455                    }
456                }
457            }
458            result
459        } else {
460            name.to_string()
461        }
462    }
463
464    fn append_index_and_abandon_function<'a>(
465        name: &str,
466        fields: impl Iterator<Item = &'a (String, RustType)>,
467        definitions: &mut Vec<Definition<Sql>>,
468    ) {
469        let mut children = Vec::new();
470        for (column, rust) in fields {
471            let column = Self::sql_column_name(column);
472            Self::add_index_if_applicable(name, &column, rust, definitions);
473            if let SqlType::References(other_table, other_column, _, _) = rust.to_sql().nullable() {
474                children.push((column, other_table, other_column));
475            }
476        }
477        if !children.is_empty() {
478            Self::add_abandon_children(name, children, definitions);
479        }
480    }
481
482    fn add_abandon_children(
483        name: &str,
484        children: Vec<(String, String, String)>,
485        definitions: &mut Vec<Definition<Sql>>,
486    ) {
487        definitions.push(Definition(
488            Self::sql_definition_name(&format!("DelChilds_{}", name)),
489            Sql::AbandonChildrenFunction(name.into(), children),
490        ));
491    }
492
493    fn add_silently_prevent_any_delete(name: &str, definitions: &mut Vec<Definition<Sql>>) {
494        definitions.push(Definition(
495            Self::sql_definition_name(&format!("SilentlyPreventAnyDeleteOn{}", name)),
496            Sql::SilentlyPreventAnyDelete(name.into()),
497        ));
498    }
499
500    pub fn has_no_column_in_embedded_struct(rust: &RustType) -> bool {
501        rust.is_vec()
502    }
503
504    pub fn is_primitive(rust: &RustType) -> bool {
505        #[allow(clippy::match_same_arms)] // to have the same order as the original enum
506        match rust.as_inner_type() {
507            RustType::String(..) => true,
508            RustType::VecU8(_) => true,
509            RustType::BitVec(_) => true,
510            RustType::Null => true,
511            r => r.is_primitive(),
512        }
513    }
514
515    pub fn struct_list_entry_table_name(struct_name: &str, field_name: &str) -> String {
516        Self::sql_definition_name(&format!(
517            "{}_{}",
518            struct_name,
519            RustCodeGenerator::rust_variant_name(field_name)
520        ))
521    }
522}
523
524pub trait ToSqlModel {
525    fn to_sql(&self) -> Model<Sql>;
526}
527
528impl ToSqlModel for Model<Rust> {
529    fn to_sql(&self) -> Model<Sql> {
530        Model::convert_rust_to_sql(self)
531    }
532}
533
534#[allow(clippy::module_name_repetitions)]
535pub trait ToSql {
536    fn to_sql(&self) -> SqlType;
537}
538
539impl ToSql for RustType {
540    fn to_sql(&self) -> SqlType {
541        SqlType::NotNull(Box::new(match self {
542            RustType::Bool => SqlType::Boolean,
543            RustType::Null => return SqlType::NullByteArray.nullable(),
544            RustType::U8(_) | RustType::I8(_) => SqlType::SmallInt,
545            RustType::U16(Range(_, upper, _)) if *upper <= i16::MAX as u16 => SqlType::SmallInt,
546            RustType::I16(_) => SqlType::SmallInt,
547            RustType::U32(Range(_, upper, _)) if *upper <= i32::MAX as u32 => SqlType::Integer,
548            RustType::U16(_) | RustType::I32(_) => SqlType::Integer,
549            RustType::U32(_) | RustType::U64(_) | RustType::I64(_) => SqlType::BigInt,
550            RustType::String(_size, _charset) => SqlType::Text,
551            RustType::VecU8(_) => SqlType::ByteArray,
552            RustType::BitVec(_) => SqlType::BitsReprByByteArrayAndBitsLen,
553            RustType::Vec(inner, _size, _ordering) => SqlType::Array(inner.to_sql().into()),
554            RustType::Option(inner) => return inner.to_sql().nullable(),
555            RustType::Default(inner, ..) => return inner.to_sql(),
556            RustType::Complex(name, _tag) => SqlType::References(
557                name.clone(),
558                FOREIGN_KEY_DEFAULT_COLUMN.into(),
559                Some(Action::Cascade),
560                Some(Action::Cascade),
561            ),
562        }))
563    }
564}
565
566#[cfg(test)]
567mod tests {
568    use super::*;
569    use crate::model::rust::{EncodingOrdering, Field};
570    use crate::model::{Charset, Model, Tag};
571    use crate::model::{Import, Size};
572
573    #[test]
574    fn test_conversion_too_long_name() {
575        let chars_70_length =
576            "TheQuickBrownFoxJumpsOverTheLazyDogTheQuickBrownFoxJumpsOverTheLazyDog";
577
578        assert!(chars_70_length.chars().count() > TYPENAME_LENGTH_LIMIT_PSQL);
579
580        assert_eq!(
581            "TQBFoxJumpsOverTheLazyDogTheQuickBrownFoxJumpsOverTheLazyDog",
582            &Model::<Sql>::sql_definition_name(chars_70_length)
583        );
584
585        assert_eq!(
586            "THEQUICKBROWNFOXJUMPSOVERTHELAZYDOGTHEQUICKBROWNFOXJUMPSOVERTHE",
587            &Model::<Sql>::sql_definition_name(&chars_70_length.to_ascii_uppercase())
588        );
589
590        assert_eq!(
591            "TheQuickBrownFoxJumpsOverTheLazyDogTheQuickBrownFoxJumpsOverThe",
592            &Model::<Sql>::sql_definition_name(&chars_70_length[..TYPENAME_LENGTH_LIMIT_PSQL])
593        );
594    }
595
596    #[test]
597    fn test_conversion_struct() {
598        let model = Model {
599            name: "Manfred".into(),
600            imports: vec![Import {
601                what: vec!["a".into(), "b".into()],
602                from: "to_be_ignored".into(),
603                from_oid: None,
604            }],
605            definitions: vec![Definition(
606                "Person".into(),
607                Rust::struct_from_fields(vec![
608                    Field::from_name_type("name", RustType::String(Size::Any, Charset::Utf8)),
609                    Field::from_name_type("birth", RustType::Complex("City".into(), None)),
610                ]),
611            )],
612            ..Default::default()
613        }
614        .to_sql();
615        assert_eq!("Manfred", &model.name);
616        assert!(model.imports.is_empty());
617        assert_eq!(
618            &vec![
619                Definition(
620                    "Person".into(),
621                    Sql::Table(
622                        vec![
623                            Column {
624                                name: "id".into(),
625                                sql: SqlType::Serial,
626                                primary_key: true
627                            },
628                            Column {
629                                name: "name".into(),
630                                sql: SqlType::NotNull(SqlType::Text.into()),
631                                primary_key: false
632                            },
633                            Column {
634                                name: "birth".into(),
635                                sql: SqlType::NotNull(
636                                    SqlType::References(
637                                        "City".into(),
638                                        FOREIGN_KEY_DEFAULT_COLUMN.into(),
639                                        Some(Action::Cascade),
640                                        Some(Action::Cascade),
641                                    )
642                                    .into()
643                                ),
644                                primary_key: false
645                            },
646                        ],
647                        vec![]
648                    )
649                ),
650                Definition(
651                    "Person_Index_birth".to_string(),
652                    Sql::Index("Person".into(), vec!["birth".into()])
653                ),
654                Definition(
655                    "DelChilds_Person".into(),
656                    Sql::AbandonChildrenFunction(
657                        "Person".into(),
658                        vec![("birth".into(), "City".into(), "id".into())],
659                    )
660                )
661            ],
662            &model.definitions
663        );
664    }
665
666    #[test]
667    fn test_conversion_data_enum() {
668        let model = Model {
669            name: "Hurray".into(),
670            imports: vec![Import {
671                what: vec!["a".into(), "b".into()],
672                from: "to_be_ignored".into(),
673                from_oid: None,
674            }],
675            definitions: vec![Definition(
676                "PersonState".into(),
677                Rust::DataEnum(
678                    vec![
679                        DataVariant::from_name_type(
680                            "DeadSince",
681                            RustType::String(Size::Any, Charset::Utf8),
682                        ),
683                        DataVariant::from_name_type(
684                            "Alive",
685                            RustType::Complex("Person".into(), Some(Tag::DEFAULT_UTF8_STRING)),
686                        ),
687                    ]
688                    .into(),
689                ),
690            )],
691            ..Default::default()
692        }
693        .to_sql();
694        assert_eq!("Hurray", &model.name);
695        assert!(model.imports.is_empty());
696        assert_eq!(
697            &vec![
698                Definition(
699                    "PersonState".into(),
700                    Sql::Table(
701                        vec![
702                            Column {
703                                name: "id".into(),
704                                sql: SqlType::Serial,
705                                primary_key: true
706                            },
707                            Column {
708                                name: "dead_since".into(),
709                                sql: SqlType::Text,
710                                primary_key: false
711                            },
712                            Column {
713                                name: "alive".into(),
714                                sql: SqlType::References(
715                                    "Person".into(),
716                                    FOREIGN_KEY_DEFAULT_COLUMN.into(),
717                                    Some(Action::Cascade),
718                                    Some(Action::Cascade),
719                                ),
720                                primary_key: false
721                            },
722                        ],
723                        vec![Constraint::OneNotNull(vec![
724                            "dead_since".into(),
725                            "alive".into(),
726                        ])]
727                    )
728                ),
729                Definition(
730                    "PersonState_Index_alive".to_string(),
731                    Sql::Index("PersonState".into(), vec!["alive".into()])
732                ),
733                Definition(
734                    "DelChilds_PersonState".into(),
735                    Sql::AbandonChildrenFunction(
736                        "PersonState".into(),
737                        vec![("alive".into(), "Person".into(), "id".into())],
738                    )
739                )
740            ],
741            &model.definitions
742        );
743    }
744
745    #[test]
746    fn test_conversion_enum() {
747        let model = Model {
748            name: "Alfred".into(),
749            imports: vec![Import {
750                what: vec!["a".into(), "b".into()],
751                from: "to_be_ignored".into(),
752                from_oid: None,
753            }],
754            definitions: vec![Definition(
755                "City".into(),
756                Rust::Enum(vec!["Esslingen".into(), "Stuttgart".into()].into()),
757            )],
758            ..Default::default()
759        }
760        .to_sql();
761        assert_eq!("Alfred", &model.name);
762        assert!(model.imports.is_empty());
763        assert_eq!(
764            &vec![
765                Definition(
766                    "City".into(),
767                    Sql::Enum(vec!["Esslingen".into(), "Stuttgart".into(),],)
768                ),
769                Definition(
770                    "SilentlyPreventAnyDeleteOnCity".into(),
771                    Sql::SilentlyPreventAnyDelete("City".into())
772                )
773            ],
774            &model.definitions
775        );
776    }
777
778    #[test]
779    fn test_conversion_struct_with_vec() {
780        let model = Model {
781            name: "Bernhard".into(),
782            imports: vec![],
783            definitions: vec![Definition(
784                "SomeStruct".into(),
785                Rust::struct_from_fields(vec![
786                    Field::from_name_type(
787                        "list_of_primitive",
788                        RustType::Vec(
789                            Box::new(RustType::String(Size::Any, Charset::Utf8)),
790                            Size::Any,
791                            EncodingOrdering::Keep,
792                        ),
793                    ),
794                    Field::from_name_type(
795                        "list_of_reference",
796                        RustType::Vec(
797                            Box::new(RustType::Complex(
798                                "ComplexType".into(),
799                                Some(Tag::DEFAULT_UTF8_STRING),
800                            )),
801                            Size::Any,
802                            EncodingOrdering::Keep,
803                        ),
804                    ),
805                ]),
806            )],
807            ..Default::default()
808        }
809        .to_sql();
810        assert_eq!("Bernhard", &model.name);
811        assert!(model.imports.is_empty());
812        assert_eq!(
813            &model.definitions,
814            &vec![
815                Definition(
816                    "SomeStruct".into(),
817                    Sql::Table(
818                        vec![Column {
819                            name: "id".into(),
820                            sql: SqlType::Serial,
821                            primary_key: true
822                        }],
823                        vec![],
824                    )
825                ),
826                Definition(
827                    "SomeStruct_ListOfPrimitive".into(),
828                    Sql::Table(
829                        vec![
830                            Column {
831                                name: "list".into(),
832                                sql: SqlType::References(
833                                    "SomeStruct".into(),
834                                    "id".into(),
835                                    Some(Action::Cascade),
836                                    Some(Action::Cascade),
837                                )
838                                .not_null(),
839                                primary_key: false,
840                            },
841                            Column {
842                                name: "value".into(),
843                                sql: SqlType::Text.not_null(),
844                                primary_key: false,
845                            },
846                        ],
847                        vec![Constraint::CombinedPrimaryKey(vec![
848                            "list".into(),
849                            "value".into()
850                        ])]
851                    )
852                ),
853                Definition(
854                    "SomeStruct_ListOfReference".into(),
855                    Sql::Table(
856                        vec![
857                            Column {
858                                name: "list".into(),
859                                sql: SqlType::References(
860                                    "SomeStruct".into(),
861                                    "id".into(),
862                                    Some(Action::Cascade),
863                                    Some(Action::Cascade),
864                                )
865                                .not_null(),
866                                primary_key: false,
867                            },
868                            Column {
869                                name: "value".into(),
870                                sql: SqlType::References(
871                                    "ComplexType".into(),
872                                    "id".into(),
873                                    Some(Action::Cascade),
874                                    Some(Action::Cascade)
875                                )
876                                .not_null(),
877                                primary_key: false,
878                            },
879                        ],
880                        vec![Constraint::CombinedPrimaryKey(vec![
881                            "list".into(),
882                            "value".into()
883                        ])]
884                    )
885                ),
886                Definition(
887                    "SomeStruct_ListOfPrimitive_Index_list".to_string(),
888                    Sql::Index("SomeStruct_ListOfPrimitive".into(), vec!["list".into()])
889                ),
890                Definition(
891                    "SomeStruct_ListOfPrimitive_Index_value".to_string(),
892                    Sql::Index("SomeStruct_ListOfPrimitive".into(), vec!["value".into()])
893                ),
894                Definition(
895                    "SomeStruct_ListOfReference_Index_list".to_string(),
896                    Sql::Index("SomeStruct_ListOfReference".into(), vec!["list".into()])
897                ),
898                Definition(
899                    "SomeStruct_ListOfReference_Index_value".to_string(),
900                    Sql::Index("SomeStruct_ListOfReference".into(), vec!["value".into()])
901                ),
902                Definition(
903                    "DelChilds_SomeStruct_ListOfReference".into(),
904                    Sql::AbandonChildrenFunction(
905                        "SomeStruct_ListOfReference".into(),
906                        vec![("value".into(), "ComplexType".into(), "id".into())]
907                    )
908                )
909            ],
910        );
911    }
912
913    #[test]
914    fn test_conversion_tuple_struct() {
915        let model = Model {
916            name: "Hurray".into(),
917            imports: vec![Import {
918                what: vec!["a".into(), "b".into()],
919                from: "to_be_ignored".into(),
920                from_oid: None,
921            }],
922            definitions: vec![
923                Definition(
924                    "Whatever".into(),
925                    Rust::tuple_struct_from_type(RustType::String(Size::Any, Charset::Utf8)),
926                ),
927                Definition(
928                    "Whatelse".into(),
929                    Rust::tuple_struct_from_type(RustType::Complex(
930                        "Whatever".into(),
931                        Some(Tag::DEFAULT_UTF8_STRING),
932                    )),
933                ),
934            ],
935            ..Default::default()
936        }
937        .to_sql();
938        assert_eq!("Hurray", &model.name);
939        assert!(model.imports.is_empty());
940        assert_eq!(
941            &vec![
942                Definition(
943                    "Whatever".into(),
944                    Sql::Table(
945                        vec![Column {
946                            name: "id".into(),
947                            sql: SqlType::Serial,
948                            primary_key: true
949                        },],
950                        vec![]
951                    )
952                ),
953                Definition(
954                    "Whatelse".into(),
955                    Sql::Table(
956                        vec![Column {
957                            name: "id".into(),
958                            sql: SqlType::Serial,
959                            primary_key: true
960                        },],
961                        vec![]
962                    )
963                ),
964                Definition(
965                    "WhateverListEntry".into(),
966                    Sql::Table(
967                        vec![
968                            Column {
969                                name: "list".into(),
970                                sql: SqlType::NotNull(
971                                    SqlType::References(
972                                        "Whatever".into(),
973                                        "id".into(),
974                                        Some(Action::Cascade),
975                                        Some(Action::Cascade),
976                                    )
977                                    .into()
978                                ),
979                                primary_key: false
980                            },
981                            Column {
982                                name: "value".into(),
983                                sql: SqlType::NotNull(SqlType::Text.into()),
984                                primary_key: false
985                            },
986                        ],
987                        vec![Constraint::CombinedPrimaryKey(vec![
988                            "list".into(),
989                            "value".into()
990                        ])]
991                    )
992                ),
993                Definition(
994                    "WhatelseListEntry".into(),
995                    Sql::Table(
996                        vec![
997                            Column {
998                                name: TUPLE_LIST_ENTRY_PARENT_COLUMN.into(),
999                                sql: SqlType::NotNull(
1000                                    SqlType::References(
1001                                        "Whatelse".into(),
1002                                        "id".into(),
1003                                        Some(Action::Cascade),
1004                                        Some(Action::Cascade),
1005                                    )
1006                                    .into()
1007                                ),
1008                                primary_key: false
1009                            },
1010                            Column {
1011                                name: TUPLE_LIST_ENTRY_VALUE_COLUMN.into(),
1012                                sql: SqlType::NotNull(
1013                                    SqlType::References(
1014                                        "Whatever".into(),
1015                                        FOREIGN_KEY_DEFAULT_COLUMN.into(),
1016                                        Some(Action::Cascade),
1017                                        Some(Action::Cascade),
1018                                    )
1019                                    .into()
1020                                ),
1021                                primary_key: false
1022                            },
1023                        ],
1024                        vec![Constraint::CombinedPrimaryKey(vec![
1025                            TUPLE_LIST_ENTRY_PARENT_COLUMN.into(),
1026                            TUPLE_LIST_ENTRY_VALUE_COLUMN.into()
1027                        ])]
1028                    )
1029                ),
1030                Definition(
1031                    format!("WhateverListEntry_Index_{}", TUPLE_LIST_ENTRY_PARENT_COLUMN),
1032                    Sql::Index(
1033                        "WhateverListEntry".into(),
1034                        vec![TUPLE_LIST_ENTRY_PARENT_COLUMN.into()]
1035                    )
1036                ),
1037                Definition(
1038                    format!("WhateverListEntry_Index_{}", TUPLE_LIST_ENTRY_VALUE_COLUMN),
1039                    Sql::Index(
1040                        "WhateverListEntry".into(),
1041                        vec![TUPLE_LIST_ENTRY_VALUE_COLUMN.into()]
1042                    )
1043                ),
1044                Definition(
1045                    format!("WhatelseListEntry_Index_{}", TUPLE_LIST_ENTRY_PARENT_COLUMN),
1046                    Sql::Index(
1047                        "WhatelseListEntry".into(),
1048                        vec![TUPLE_LIST_ENTRY_PARENT_COLUMN.into()]
1049                    )
1050                ),
1051                Definition(
1052                    format!("WhatelseListEntry_Index_{}", TUPLE_LIST_ENTRY_VALUE_COLUMN),
1053                    Sql::Index(
1054                        "WhatelseListEntry".into(),
1055                        vec![TUPLE_LIST_ENTRY_VALUE_COLUMN.into()]
1056                    )
1057                ),
1058                Definition(
1059                    "DelChilds_WhatelseListEntry".into(),
1060                    Sql::AbandonChildrenFunction(
1061                        "WhatelseListEntry".into(),
1062                        vec![(
1063                            TUPLE_LIST_ENTRY_VALUE_COLUMN.into(),
1064                            "Whatever".into(),
1065                            "id".into()
1066                        )],
1067                    )
1068                )
1069            ],
1070            &model.definitions
1071        );
1072    }
1073
1074    #[test]
1075    fn test_conversion_on_first_level_name_clash() {
1076        let model = Model {
1077            name: "Alfred".into(),
1078            imports: vec![Import {
1079                what: vec!["a".into(), "b".into()],
1080                from: "to_be_ignored".into(),
1081                from_oid: None,
1082            }],
1083            definitions: vec![Definition(
1084                "City".into(),
1085                Rust::struct_from_fields(vec![Field::from_name_type(
1086                    "id",
1087                    RustType::String(Size::Any, Charset::Utf8),
1088                )]),
1089            )],
1090            ..Default::default()
1091        }
1092        .to_sql();
1093        assert_eq!("Alfred", &model.name);
1094        assert!(model.imports.is_empty());
1095        assert_eq!(
1096            &vec![Definition(
1097                "City".into(),
1098                Sql::Table(
1099                    vec![
1100                        Column {
1101                            name: FOREIGN_KEY_DEFAULT_COLUMN.into(),
1102                            sql: SqlType::Serial,
1103                            primary_key: true
1104                        },
1105                        Column {
1106                            name: "id_".into(),
1107                            sql: SqlType::NotNull(SqlType::Text.into()),
1108                            primary_key: false
1109                        },
1110                    ],
1111                    vec![]
1112                )
1113            ),],
1114            &model.definitions
1115        );
1116    }
1117
1118    #[test]
1119    fn test_rust_to_sql_to_rust() {
1120        assert_eq!(RustType::Bool.to_sql().to_rust(), RustType::Bool);
1121        assert_eq!(
1122            RustType::I8(Range::inclusive(0, i8::MAX))
1123                .to_sql()
1124                .to_rust(),
1125            RustType::I16(Range::inclusive(0, i16::MAX))
1126        );
1127        assert_eq!(
1128            RustType::U8(Range::inclusive(0, u8::MAX))
1129                .to_sql()
1130                .to_rust(),
1131            RustType::I16(Range::inclusive(0, i16::MAX))
1132        );
1133        assert_eq!(
1134            RustType::I16(Range::inclusive(0, i16::MAX))
1135                .to_sql()
1136                .to_rust(),
1137            RustType::I16(Range::inclusive(0, i16::MAX))
1138        );
1139        assert_eq!(
1140            RustType::U16(Range::inclusive(0, i16::MAX as u16))
1141                .to_sql()
1142                .to_rust(),
1143            RustType::I16(Range::inclusive(0, i16::MAX))
1144        );
1145        assert_eq!(
1146            RustType::U16(Range::inclusive(0, u16::MAX))
1147                .to_sql()
1148                .to_rust(),
1149            RustType::I32(Range::inclusive(0, i32::MAX))
1150        );
1151        assert_eq!(
1152            RustType::I32(Range::inclusive(0, i32::MAX))
1153                .to_sql()
1154                .to_rust(),
1155            RustType::I32(Range::inclusive(0, i32::MAX))
1156        );
1157        assert_eq!(
1158            RustType::U32(Range::inclusive(0, i32::MAX as u32))
1159                .to_sql()
1160                .to_rust(),
1161            RustType::I32(Range::inclusive(0, i32::MAX))
1162        );
1163        assert_eq!(
1164            RustType::U32(Range::inclusive(0, u32::MAX))
1165                .to_sql()
1166                .to_rust(),
1167            RustType::I64(Range::inclusive(0, i64::MAX))
1168        );
1169        assert_eq!(
1170            RustType::I64(Range::inclusive(0, i64::MAX))
1171                .to_sql()
1172                .to_rust(),
1173            RustType::I64(Range::inclusive(0, i64::MAX))
1174        );
1175        assert_eq!(
1176            RustType::U64(Range::none()).to_sql().to_rust(),
1177            RustType::I64(Range::inclusive(0, i64::MAX))
1178        );
1179        assert_eq!(
1180            RustType::U64(Range::inclusive(Some(0), Some(u64::MAX)))
1181                .to_sql()
1182                .to_rust(),
1183            RustType::I64(Range::inclusive(0, i64::MAX))
1184        );
1185
1186        assert_eq!(
1187            RustType::String(Size::Any, Charset::Utf8)
1188                .to_sql()
1189                .to_rust(),
1190            RustType::String(Size::Any, Charset::Utf8),
1191        );
1192        assert_eq!(
1193            RustType::VecU8(Size::Any).to_sql().to_rust(),
1194            RustType::VecU8(Size::Any)
1195        );
1196        assert_eq!(
1197            RustType::Vec(
1198                Box::new(RustType::String(Size::Any, Charset::Utf8)),
1199                Size::Any,
1200                EncodingOrdering::Keep
1201            )
1202            .to_sql()
1203            .to_rust(),
1204            RustType::Vec(
1205                Box::new(RustType::String(Size::Any, Charset::Utf8)),
1206                Size::Any,
1207                EncodingOrdering::Keep
1208            ),
1209        );
1210        assert_eq!(
1211            RustType::Option(Box::new(RustType::VecU8(Size::Any)))
1212                .to_sql()
1213                .to_rust(),
1214            RustType::Option(Box::new(RustType::VecU8(Size::Any))),
1215        );
1216        assert_eq!(
1217            RustType::Complex("MuchComplex".into(), None)
1218                .to_sql()
1219                .to_rust(),
1220            RustType::Complex("MuchComplex".into(), None),
1221        );
1222    }
1223
1224    #[test]
1225    fn test_sql_to_rust() {
1226        // only cases that are not already tested by above
1227        assert_eq!(
1228            SqlType::NotNull(SqlType::Serial.into()).to_rust(),
1229            RustType::I32(Range::inclusive(0, i32::MAX))
1230        );
1231    }
1232
1233    #[test]
1234    fn test_nullable() {
1235        assert_eq!(
1236            SqlType::NotNull(SqlType::Serial.into()).nullable(),
1237            SqlType::Serial
1238        );
1239        assert_eq!(SqlType::Serial.nullable(), SqlType::Serial);
1240    }
1241
1242    #[test]
1243    fn test_to_string() {
1244        assert_eq!("SMALLINT", &SqlType::SmallInt.to_string());
1245        assert_eq!("INTEGER", &SqlType::Integer.to_string());
1246        assert_eq!("BIGINT", &SqlType::BigInt.to_string());
1247        assert_eq!("SERIAL", &SqlType::Serial.to_string());
1248        assert_eq!("BOOLEAN", &SqlType::Boolean.to_string());
1249        assert_eq!("TEXT", &SqlType::Text.to_string());
1250        assert_eq!(
1251            "SMALLINT[]",
1252            &SqlType::Array(SqlType::SmallInt.into()).to_string()
1253        );
1254        assert_eq!(
1255            "TEXT NOT NULL",
1256            &SqlType::NotNull(SqlType::Text.into()).to_string()
1257        );
1258        assert_eq!(
1259            "INTEGER REFERENCES tablo(columno)",
1260            &SqlType::References("tablo".into(), "columno".into(), None, None).to_string()
1261        );
1262        assert_eq!(
1263            "INTEGER REFERENCES tablo(columno) ON DELETE CASCADE ON UPDATE RESTRICT",
1264            &SqlType::References(
1265                "tablo".into(),
1266                "columno".into(),
1267                Some(Action::Cascade),
1268                Some(Action::Restrict),
1269            )
1270            .to_string()
1271        );
1272        assert_eq!(
1273            "INTEGER REFERENCES table(column) NOT NULL",
1274            &SqlType::NotNull(
1275                SqlType::References("table".into(), "column".into(), None, None).into()
1276            )
1277            .to_string()
1278        );
1279        assert_eq!(
1280            "INTEGER REFERENCES table(column) ON DELETE RESTRICT ON UPDATE CASCADE NOT NULL",
1281            &SqlType::NotNull(
1282                SqlType::References(
1283                    "table".into(),
1284                    "column".into(),
1285                    Some(Action::Restrict),
1286                    Some(Action::Cascade),
1287                )
1288                .into()
1289            )
1290            .to_string()
1291        );
1292    }
1293}