rorm_sql/
create_column.rs

1use std::fmt::Write;
2
3use rorm_declaration::imr::DefaultValue;
4
5#[cfg(feature = "postgres")]
6use crate::create_trigger::trigger_annotation_to_trigger_postgres;
7#[cfg(feature = "sqlite")]
8use crate::create_trigger::trigger_annotation_to_trigger_sqlite;
9#[cfg(feature = "mysql")]
10use crate::db_specific::mysql;
11#[cfg(feature = "postgres")]
12use crate::db_specific::postgres;
13#[cfg(feature = "sqlite")]
14use crate::db_specific::sqlite;
15use crate::error::Error;
16use crate::{Annotation, DbType, Value};
17
18/**
19Trait representing the create table builder.
20*/
21pub trait CreateColumn<'post_build>: Sized {
22    /**
23    Builds the column based on the data.
24
25    **Parameter**:
26    - `s`: mutable reference to a String to write the operation to
27    */
28    fn build(self, s: &mut String) -> Result<(), Error>;
29}
30
31/**
32Representation of an annotation
33 */
34#[derive(Debug)]
35pub struct SQLAnnotation<'post_build> {
36    pub(crate) annotation: &'post_build Annotation,
37}
38
39/**
40Representation of the data of the creation of a column for the sqlite dialect
41 */
42#[derive(Debug)]
43#[cfg(feature = "sqlite")]
44pub struct CreateColumnSQLiteData<'until_build, 'post_build> {
45    pub(crate) name: &'until_build str,
46    pub(crate) table_name: &'until_build str,
47    pub(crate) data_type: DbType,
48    pub(crate) annotations: Vec<SQLAnnotation<'post_build>>,
49    pub(crate) statements: Option<&'until_build mut Vec<(String, Vec<Value<'post_build>>)>>,
50    pub(crate) lookup: Option<&'until_build mut Vec<Value<'post_build>>>,
51}
52
53/**
54Representation of the data of the creation of a column for the mysql dialect
55 */
56#[derive(Debug)]
57#[cfg(feature = "mysql")]
58pub struct CreateColumnMySQLData<'until_build, 'post_build> {
59    pub(crate) name: &'until_build str,
60    pub(crate) data_type: DbType,
61    pub(crate) annotations: Vec<SQLAnnotation<'post_build>>,
62    pub(crate) statements: Option<&'until_build mut Vec<(String, Vec<Value<'post_build>>)>>,
63    pub(crate) lookup: Option<&'until_build mut Vec<Value<'post_build>>>,
64}
65
66/**
67Representation of the data of the creation of a column for the mysql dialect
68 */
69#[derive(Debug)]
70#[cfg(feature = "postgres")]
71pub struct CreateColumnPostgresData<'until_build, 'post_build> {
72    pub(crate) name: &'until_build str,
73    pub(crate) table_name: &'until_build str,
74    pub(crate) data_type: DbType,
75    pub(crate) annotations: Vec<SQLAnnotation<'post_build>>,
76    pub(crate) pre_statements: Option<&'until_build mut Vec<(String, Vec<Value<'post_build>>)>>,
77    pub(crate) statements: Option<&'until_build mut Vec<(String, Vec<Value<'post_build>>)>>,
78}
79
80/**
81Representation of the different implementations of the [CreateColumn] trait.
82
83Should only be constructed via [crate::DBImpl::create_column].
84*/
85#[derive(Debug)]
86pub enum CreateColumnImpl<'until_build, 'post_build> {
87    /**
88    SQLite representation of the create column operation.
89     */
90    #[cfg(feature = "sqlite")]
91    SQLite(CreateColumnSQLiteData<'until_build, 'post_build>),
92    /**
93    MySQL representation of the create column operation.
94     */
95    #[cfg(feature = "mysql")]
96    MySQL(CreateColumnMySQLData<'until_build, 'post_build>),
97    /**
98    Postgres representation of the create column operation.
99     */
100    #[cfg(feature = "postgres")]
101    Postgres(CreateColumnPostgresData<'until_build, 'post_build>),
102}
103
104impl<'post_build> CreateColumn<'post_build> for CreateColumnImpl<'_, 'post_build> {
105    fn build(self, s: &mut String) -> Result<(), Error> {
106        match self {
107            #[cfg(feature = "sqlite")]
108            CreateColumnImpl::SQLite(mut d) => {
109                write!(
110                    s,
111                    "\"{}\" {} ",
112                    d.name,
113                    match d.data_type {
114                        DbType::Binary | DbType::Uuid => "BLOB",
115                        DbType::VarChar
116                        | DbType::Date
117                        | DbType::DateTime
118                        | DbType::Timestamp
119                        | DbType::Time
120                        | DbType::Choices => "TEXT",
121                        DbType::Int8
122                        | DbType::Int16
123                        | DbType::Int32
124                        | DbType::Int64
125                        | DbType::Boolean => "INTEGER",
126                        DbType::Float | DbType::Double => "REAL",
127                        DbType::BitVec | DbType::MacAddress | DbType::IpNetwork => unreachable!(
128                            "BitVec, MacAddress and IpNetwork are not available for sqlite"
129                        ),
130                    }
131                )
132                .unwrap();
133
134                for (idx, x) in d.annotations.iter().enumerate() {
135                    if let Some(ref mut s) = d.statements {
136                        trigger_annotation_to_trigger_sqlite(
137                            x.annotation,
138                            &d.data_type,
139                            d.table_name,
140                            d.name,
141                            s,
142                        );
143                    }
144
145                    match &x.annotation {
146                        Annotation::AutoIncrement => write!(s, "AUTOINCREMENT").unwrap(),
147                        Annotation::AutoCreateTime => {
148                            write!(
149                                s,
150                                "DEFAULT {}",
151                                match d.data_type {
152                                    DbType::Date => "CURRENT_DATE",
153                                    DbType::DateTime => "CURRENT_TIMESTAMP",
154                                    DbType::Timestamp => "CURRENT_TIMESTAMP",
155                                    DbType::Time => "CURRENT_TIME",
156                                    _ => "",
157                                }
158                            )
159                            .unwrap();
160                        }
161                        Annotation::DefaultValue(d) => match d {
162                            DefaultValue::String(dv) => {
163                                write!(s, "DEFAULT {}", sqlite::fmt(dv)).unwrap()
164                            }
165                            DefaultValue::Integer(i) => write!(s, "DEFAULT {i}").unwrap(),
166                            DefaultValue::Float(f) => write!(s, "DEFAULT {f}").unwrap(),
167                            DefaultValue::Boolean(b) => {
168                                if *b {
169                                    write!(s, "DEFAULT 1").unwrap();
170                                } else {
171                                    write!(s, "DEFAULT 0").unwrap();
172                                }
173                            }
174                        },
175                        Annotation::NotNull => write!(s, "NOT NULL").unwrap(),
176                        Annotation::PrimaryKey => write!(s, "PRIMARY KEY").unwrap(),
177                        Annotation::Unique => write!(s, "UNIQUE").unwrap(),
178                        Annotation::ForeignKey(fk) => write!(
179                            s,
180                            "REFERENCES \"{}\" (\"{}\") ON DELETE {} ON UPDATE {}",
181                            fk.table_name, fk.column_name, fk.on_delete, fk.on_update
182                        )
183                        .unwrap(),
184                        _ => {}
185                    }
186
187                    if idx != d.annotations.len() - 1 {
188                        write!(s, " ").unwrap();
189                    }
190                }
191
192                Ok(())
193            }
194            #[cfg(feature = "mysql")]
195            CreateColumnImpl::MySQL(mut d) => {
196                write!(s, "`{}` ", d.name).unwrap();
197
198                match d.data_type {
199                    DbType::VarChar => {
200                        let a_opt = d
201                            .annotations
202                            .iter()
203                            .find(|x| x.annotation.eq_shallow(&Annotation::MaxLength(0)));
204
205                        if let Some(a) = a_opt {
206                            if let Annotation::MaxLength(max_length) = a.annotation {
207                                // utf8mb4 is 4 bytes wide, so that's the maximum for varchar
208                                if *max_length < 2i32.pow(14) - 1 {
209                                    write!(s, "VARCHAR({max_length}) ").unwrap();
210                                } else {
211                                    write!(s, "LONGTEXT ").unwrap();
212                                }
213                            } else {
214                                return Err(Error::SQLBuildError(String::from(
215                                    "VARCHAR must have a max_length annotation",
216                                )));
217                            }
218                        } else {
219                            return Err(Error::SQLBuildError(String::from(
220                                "VARCHAR must have a max_length annotation",
221                            )));
222                        }
223                    }
224                    DbType::Binary | DbType::Uuid => write!(s, "LONGBLOB ").unwrap(),
225                    DbType::Int8 => write!(s, "TINYINT(255) ").unwrap(),
226                    DbType::Int16 => write!(s, "SMALLINT(255) ").unwrap(),
227                    DbType::Int32 => write!(s, "INT(255) ").unwrap(),
228                    DbType::Int64 => write!(s, "BIGINT(255) ").unwrap(),
229                    DbType::Float => write!(s, "FLOAT(24) ").unwrap(),
230                    DbType::Double => write!(s, "DOUBLE(53) ").unwrap(),
231                    DbType::Boolean => write!(s, "BOOL ").unwrap(),
232                    DbType::Date => write!(s, "DATE ").unwrap(),
233                    DbType::DateTime => write!(s, "DATETIME ").unwrap(),
234                    DbType::Timestamp => write!(s, "TIMESTAMP ").unwrap(),
235                    DbType::Time => write!(s, "TIME ").unwrap(),
236                    DbType::Choices => {
237                        let a_opt = d.annotations.iter().find(|x| {
238                            x.annotation
239                                .eq_shallow(&Annotation::Choices(Default::default()))
240                        });
241
242                        if let Some(a) = a_opt {
243                            if let Annotation::Choices(values) = a.annotation {
244                                write!(
245                                    s,
246                                    "ENUM({}) ",
247                                    values
248                                        .iter()
249                                        .map(|x| mysql::fmt(x))
250                                        .collect::<Vec<String>>()
251                                        .join(", ")
252                                )
253                                .unwrap();
254                            } else {
255                                return Err(Error::SQLBuildError(
256                                    "VARCHAR must have a MaxLength annotation".to_string(),
257                                ));
258                            }
259                        } else {
260                            return Err(Error::SQLBuildError(
261                                "VARCHAR must have a MaxLength annotation".to_string(),
262                            ));
263                        }
264                    }
265                    DbType::BitVec | DbType::IpNetwork | DbType::MacAddress => {
266                        unreachable!("BitVec, MacAddress and IpNetwork are not available for mysql")
267                    }
268                };
269
270                for (idx, x) in d.annotations.iter().enumerate() {
271                    match &x.annotation {
272                        Annotation::AutoIncrement => write!(s, "AUTO_INCREMENT").unwrap(),
273                        Annotation::AutoCreateTime => {
274                            write!(
275                                s,
276                                "DEFAULT {}",
277                                match d.data_type {
278                                    DbType::Date => "CURRENT_DATE",
279                                    DbType::DateTime => "CURRENT_TIMESTAMP",
280                                    DbType::Timestamp => "CURRENT_TIMESTAMP",
281                                    DbType::Time => "CURRENT_TIME",
282                                    _ => "",
283                                }
284                            )
285                            .unwrap();
286                        }
287                        Annotation::AutoUpdateTime => write!(
288                            s,
289                            "ON UPDATE {}",
290                            match d.data_type {
291                                DbType::Date => "CURRENT_DATE",
292                                DbType::DateTime => "CURRENT_TIMESTAMP",
293                                DbType::Timestamp => "CURRENT_TIMESTAMP",
294                                DbType::Time => "CURRENT_TIME",
295                                _ => "",
296                            }
297                        )
298                        .unwrap(),
299                        Annotation::DefaultValue(v) => match v {
300                            DefaultValue::String(dv) => {
301                                if let Some(l) = &mut d.lookup {
302                                    l.push(Value::String(dv))
303                                }
304                                write!(s, "DEFAULT ?").unwrap();
305                            }
306                            DefaultValue::Integer(i) => write!(s, "DEFAULT {i}").unwrap(),
307                            DefaultValue::Float(f) => write!(s, "DEFAULT {f}").unwrap(),
308                            DefaultValue::Boolean(b) => {
309                                if *b {
310                                    write!(s, "DEFAULT 1").unwrap();
311                                } else {
312                                    write!(s, "DEFAULT 0").unwrap();
313                                }
314                            }
315                        },
316                        Annotation::NotNull => write!(s, "NOT NULL").unwrap(),
317                        Annotation::PrimaryKey => write!(s, "PRIMARY KEY").unwrap(),
318                        Annotation::Unique => write!(s, "UNIQUE").unwrap(),
319                        Annotation::ForeignKey(fk) => write!(
320                            s,
321                            "REFERENCES `{}`(`{}`) ON DELETE {} ON UPDATE {}",
322                            fk.table_name, fk.column_name, fk.on_delete, fk.on_update
323                        )
324                        .unwrap(),
325                        _ => {}
326                    }
327
328                    if idx != d.annotations.len() - 1 {
329                        write!(s, " ").unwrap();
330                    }
331                }
332
333                Ok(())
334            }
335            #[cfg(feature = "postgres")]
336            CreateColumnImpl::Postgres(mut d) => {
337                write!(s, "\"{}\" ", d.name).unwrap();
338
339                match d.data_type {
340                    DbType::VarChar => {
341                        let a_opt = d
342                            .annotations
343                            .iter()
344                            .find(|x| x.annotation.eq_shallow(&Annotation::MaxLength(0)));
345
346                        if let Some(a) = a_opt {
347                            if let Annotation::MaxLength(max_length) = a.annotation {
348                                write!(s, "character varying ({max_length}) ").unwrap();
349                            } else {
350                                return Err(Error::SQLBuildError(
351                                    "character varying must have a max_length annotation"
352                                        .to_string(),
353                                ));
354                            }
355                        } else {
356                            return Err(Error::SQLBuildError(
357                                "character varying must have a max_length annotation".to_string(),
358                            ));
359                        }
360                    }
361                    DbType::Choices => {
362                        let a_opt = d.annotations.iter().find(|x| {
363                            x.annotation
364                                .eq_shallow(&Annotation::Choices(Default::default()))
365                        });
366
367                        if let Some(a) = a_opt {
368                            if let Annotation::Choices(values) = a.annotation {
369                                if let Some(stmts) = d.pre_statements {
370                                    stmts.push((
371                                        format!(
372                                            "CREATE TYPE _{}_{} AS ENUM({});",
373                                            d.table_name,
374                                            d.name,
375                                            values
376                                                .iter()
377                                                .map(|x| { postgres::fmt(x) })
378                                                .collect::<Vec<String>>()
379                                                .join(", ")
380                                        ),
381                                        vec![],
382                                    ));
383                                };
384                                write!(s, "_{}_{} ", d.table_name, d.name,).unwrap();
385                            } else {
386                                return Err(Error::SQLBuildError(
387                                    "VARCHAR must have a MaxLength annotation".to_string(),
388                                ));
389                            }
390                        } else {
391                            return Err(Error::SQLBuildError(
392                                "VARCHAR must have a MaxLength annotation".to_string(),
393                            ));
394                        }
395                    }
396                    DbType::Uuid => write!(s, "uuid ").unwrap(),
397                    DbType::MacAddress => write!(s, "macaddr ").unwrap(),
398                    DbType::IpNetwork => write!(s, "inet ").unwrap(),
399                    DbType::BitVec => write!(s, "varbit ").unwrap(),
400                    DbType::Binary => write!(s, "bytea ").unwrap(),
401                    DbType::Int8 => write!(s, "smallint ").unwrap(),
402                    DbType::Int16 => {
403                        if d.annotations
404                            .iter()
405                            .any(|x| x.annotation.eq_shallow(&Annotation::AutoIncrement))
406                        {
407                            write!(s, "smallserial ").unwrap();
408                        } else {
409                            write!(s, "smallint ").unwrap();
410                        }
411                    }
412                    DbType::Int32 => {
413                        if d.annotations
414                            .iter()
415                            .any(|x| x.annotation.eq_shallow(&Annotation::AutoIncrement))
416                        {
417                            write!(s, "serial ").unwrap();
418                        } else {
419                            write!(s, "integer ").unwrap();
420                        }
421                    }
422                    DbType::Int64 => {
423                        if d.annotations
424                            .iter()
425                            .any(|x| x.annotation.eq_shallow(&Annotation::AutoIncrement))
426                        {
427                            write!(s, "bigserial ").unwrap();
428                        } else {
429                            write!(s, "bigint ").unwrap();
430                        }
431                    }
432                    DbType::Float => write!(s, "real ").unwrap(),
433                    DbType::Double => write!(s, "double precision ").unwrap(),
434                    DbType::Boolean => write!(s, "boolean ").unwrap(),
435                    DbType::Date => write!(s, "date ").unwrap(),
436                    DbType::DateTime => write!(s, "timestamptz ").unwrap(),
437                    DbType::Timestamp => write!(s, "timestamp ").unwrap(),
438                    DbType::Time => write!(s, "time ").unwrap(),
439                };
440
441                for (idx, x) in d.annotations.iter().enumerate() {
442                    if let Some(ref mut s) = d.statements {
443                        trigger_annotation_to_trigger_postgres(
444                            x.annotation,
445                            d.table_name,
446                            d.name,
447                            s,
448                        );
449                    }
450
451                    match &x.annotation {
452                        Annotation::AutoCreateTime => {
453                            write!(
454                                s,
455                                "DEFAULT {}",
456                                match d.data_type {
457                                    DbType::Date => "CURRENT_DATE",
458                                    DbType::DateTime => "now()",
459                                    DbType::Timestamp => "CURRENT_TIMESTAMP",
460                                    DbType::Time => "CURRENT_TIME",
461                                    _ => "",
462                                }
463                            )
464                            .unwrap();
465                        }
466                        Annotation::DefaultValue(d) => match d {
467                            DefaultValue::String(dv) => {
468                                write!(s, "DEFAULT {}", postgres::fmt(dv)).unwrap()
469                            }
470                            DefaultValue::Integer(i) => write!(s, "DEFAULT {i}").unwrap(),
471                            DefaultValue::Float(f) => write!(s, "DEFAULT {f}").unwrap(),
472                            DefaultValue::Boolean(b) => {
473                                if *b {
474                                    write!(s, "DEFAULT true").unwrap();
475                                } else {
476                                    write!(s, "DEFAULT false").unwrap();
477                                }
478                            }
479                        },
480                        Annotation::NotNull => write!(s, "NOT NULL").unwrap(),
481                        Annotation::PrimaryKey => write!(s, "PRIMARY KEY").unwrap(),
482                        Annotation::Unique => write!(s, "UNIQUE").unwrap(),
483                        Annotation::ForeignKey(fk) => write!(
484                            s,
485                            "REFERENCES \"{}\"(\"{}\") ON DELETE {} ON UPDATE {}",
486                            fk.table_name, fk.column_name, fk.on_delete, fk.on_update
487                        )
488                        .unwrap(),
489                        _ => {}
490                    };
491
492                    if idx != d.annotations.len() - 1 {
493                        write!(s, " ").unwrap();
494                    }
495                }
496
497                Ok(())
498            }
499        }
500    }
501}