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
16const TYPENAME_LENGTH_LIMIT_PSQL: usize = 63;
19
20#[derive(Debug, Clone, PartialEq, PartialOrd)]
21#[allow(clippy::module_name_repetitions)]
22pub enum SqlType {
23 SmallInt, Integer, BigInt, Serial, 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)] 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 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(), 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 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 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 } else {
447 result.push(character);
448 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)] 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 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}