Skip to main content

rorm_sql/
create_column.rs

1use std::borrow::Cow;
2use std::fmt::Write;
3
4use rorm_declaration::imr::DefaultValue;
5
6#[cfg(feature = "postgres")]
7use crate::create_trigger::trigger_annotation_to_trigger_postgres;
8#[cfg(feature = "sqlite")]
9use crate::create_trigger::trigger_annotation_to_trigger_sqlite;
10#[cfg(feature = "postgres")]
11use crate::db_specific::postgres;
12#[cfg(feature = "sqlite")]
13use crate::db_specific::sqlite;
14use crate::error::Error;
15use crate::{Annotation, DbType, Value};
16
17/**
18Trait representing the create table builder.
19*/
20pub trait CreateColumn<'post_build>: Sized {
21    /**
22    Builds the column based on the data.
23
24    **Parameter**:
25    - `s`: mutable reference to a String to write the operation to
26    */
27    fn build(self, s: &mut String) -> Result<(), Error>;
28}
29
30/**
31Representation of an annotation
32 */
33#[derive(Debug)]
34pub struct SQLAnnotation<'post_build> {
35    pub(crate) annotation: &'post_build Annotation,
36}
37
38/**
39Representation of the data of the creation of a column for the sqlite dialect
40 */
41#[derive(Debug)]
42#[cfg(feature = "sqlite")]
43pub struct CreateColumnSQLiteData<'until_build, 'post_build> {
44    pub(crate) name: &'until_build str,
45    pub(crate) table_name: &'until_build str,
46    pub(crate) data_type: DbType,
47    pub(crate) annotations: Vec<SQLAnnotation<'post_build>>,
48    pub(crate) statements: Option<&'until_build mut Vec<(String, Vec<Value<'post_build>>)>>,
49    pub(crate) lookup: Option<&'until_build mut Vec<Value<'post_build>>>,
50}
51
52/**
53Representation of the data of the creation of a column for the mysql dialect
54 */
55#[derive(Debug)]
56#[cfg(feature = "postgres")]
57pub struct CreateColumnPostgresData<'until_build, 'post_build> {
58    pub(crate) name: &'until_build str,
59    pub(crate) table_name: &'until_build str,
60    pub(crate) data_type: DbType,
61    pub(crate) annotations: Vec<SQLAnnotation<'post_build>>,
62    pub(crate) pre_statements: Option<&'until_build mut Vec<(String, Vec<Value<'post_build>>)>>,
63    pub(crate) statements: Option<&'until_build mut Vec<(String, Vec<Value<'post_build>>)>>,
64}
65
66/**
67Representation of the different implementations of the [CreateColumn] trait.
68
69Should only be constructed via [crate::DBImpl::create_column].
70*/
71#[derive(Debug)]
72pub enum CreateColumnImpl<'until_build, 'post_build> {
73    /**
74    SQLite representation of the create column operation.
75     */
76    #[cfg(feature = "sqlite")]
77    SQLite(CreateColumnSQLiteData<'until_build, 'post_build>),
78    /**
79    Postgres representation of the create column operation.
80     */
81    #[cfg(feature = "postgres")]
82    Postgres(CreateColumnPostgresData<'until_build, 'post_build>),
83}
84
85impl<'post_build> CreateColumn<'post_build> for CreateColumnImpl<'_, 'post_build> {
86    fn build(self, sql: &mut String) -> Result<(), Error> {
87        match self {
88            #[cfg(feature = "sqlite")]
89            CreateColumnImpl::SQLite(mut column) => {
90                write!(
91                    sql,
92                    "\"{}\" {}",
93                    column.name,
94                    sqlite_type(column.data_type)?
95                )
96                .unwrap();
97
98                for x in &column.annotations {
99                    let SQLAnnotation { annotation } = x;
100
101                    if let Some(s) = &mut column.statements {
102                        trigger_annotation_to_trigger_sqlite(
103                            annotation,
104                            &column.data_type,
105                            column.table_name,
106                            column.name,
107                            s,
108                        );
109                    }
110
111                    sql.push(' ');
112                    match &annotation {
113                        Annotation::AutoIncrement => write!(sql, "AUTOINCREMENT").unwrap(),
114                        Annotation::AutoCreateTime => {
115                            write!(
116                                sql,
117                                "DEFAULT {}",
118                                match column.data_type {
119                                    DbType::Date => "CURRENT_DATE",
120                                    DbType::DateTime => "CURRENT_TIMESTAMP",
121                                    DbType::Timestamp => "CURRENT_TIMESTAMP",
122                                    DbType::Time => "CURRENT_TIME",
123                                    _ =>
124                                        return Err(Error::SQLBuildError(format!(
125                                            "AutoCreateTime not compatible with {:?}",
126                                            column.data_type
127                                        ))),
128                                }
129                            )
130                            .unwrap();
131                        }
132                        Annotation::DefaultValue(DefaultValue::String(x)) => {
133                            write!(sql, "DEFAULT {}", sqlite::fmt(x)).unwrap()
134                        }
135                        Annotation::DefaultValue(DefaultValue::Integer(x)) => {
136                            write!(sql, "DEFAULT {x}").unwrap()
137                        }
138                        Annotation::DefaultValue(DefaultValue::Float(x)) => {
139                            write!(sql, "DEFAULT {x}").unwrap()
140                        }
141                        Annotation::DefaultValue(DefaultValue::Boolean(true)) => {
142                            write!(sql, "DEFAULT 1").unwrap()
143                        }
144                        Annotation::DefaultValue(DefaultValue::Boolean(false)) => {
145                            write!(sql, "DEFAULT 0").unwrap()
146                        }
147                        Annotation::NotNull => write!(sql, "NOT NULL").unwrap(),
148                        Annotation::PrimaryKey => write!(sql, "PRIMARY KEY").unwrap(),
149                        Annotation::Unique => write!(sql, "UNIQUE").unwrap(),
150                        Annotation::ForeignKey(fk) => write!(
151                            sql,
152                            "REFERENCES \"{}\" (\"{}\") ON DELETE {} ON UPDATE {}",
153                            fk.table_name, fk.column_name, fk.on_delete, fk.on_update
154                        )
155                        .unwrap(),
156                        _ => {}
157                    }
158                }
159
160                Ok(())
161            }
162            #[cfg(feature = "postgres")]
163            CreateColumnImpl::Postgres(mut column) => {
164                write!(sql, "\"{}\" ", column.name).unwrap();
165
166                match postgres_type(
167                    column.data_type,
168                    column.annotations.iter().map(|x| x.annotation),
169                )? {
170                    PostgresType::Normal(x) => write!(sql, "{x}").unwrap(),
171                    PostgresType::Choices(values) => {
172                        if let Some(stmts) = column.pre_statements {
173                            stmts.push((
174                                format!(
175                                    "CREATE TYPE _{}_{} AS ENUM({});",
176                                    column.table_name,
177                                    column.name,
178                                    values
179                                        .iter()
180                                        .map(|x| { postgres::fmt(x) })
181                                        .collect::<Vec<String>>()
182                                        .join(", ")
183                                ),
184                                vec![],
185                            ));
186                        };
187                        write!(sql, "_{}_{}", column.table_name, column.name,).unwrap();
188                    }
189                };
190
191                for x in &column.annotations {
192                    let SQLAnnotation { annotation } = x;
193
194                    if let Some(s) = &mut column.statements {
195                        trigger_annotation_to_trigger_postgres(
196                            annotation,
197                            column.table_name,
198                            column.name,
199                            s,
200                        );
201                    }
202
203                    sql.push(' ');
204                    match &annotation {
205                        Annotation::AutoCreateTime => {
206                            write!(
207                                sql,
208                                "DEFAULT {}",
209                                match column.data_type {
210                                    DbType::Date => "CURRENT_DATE",
211                                    DbType::DateTime => "now()",
212                                    DbType::Timestamp => "CURRENT_TIMESTAMP",
213                                    DbType::Time => "CURRENT_TIME",
214                                    _ =>
215                                        return Err(Error::SQLBuildError(format!(
216                                            "AutoCreateTime not compatible with {:?}",
217                                            column.data_type
218                                        ))),
219                                }
220                            )
221                            .unwrap();
222                        }
223                        Annotation::DefaultValue(DefaultValue::String(x)) => {
224                            write!(sql, "DEFAULT {}", postgres::fmt(x)).unwrap()
225                        }
226                        Annotation::DefaultValue(DefaultValue::Integer(x)) => {
227                            write!(sql, "DEFAULT {x}").unwrap()
228                        }
229                        Annotation::DefaultValue(DefaultValue::Float(x)) => {
230                            write!(sql, "DEFAULT {x}").unwrap()
231                        }
232                        Annotation::DefaultValue(DefaultValue::Boolean(true)) => {
233                            write!(sql, "DEFAULT true").unwrap()
234                        }
235                        Annotation::DefaultValue(DefaultValue::Boolean(false)) => {
236                            write!(sql, "DEFAULT false").unwrap()
237                        }
238                        Annotation::NotNull => write!(sql, "NOT NULL").unwrap(),
239                        Annotation::PrimaryKey => write!(sql, "PRIMARY KEY").unwrap(),
240                        Annotation::Unique => write!(sql, "UNIQUE").unwrap(),
241                        Annotation::ForeignKey(fk) => write!(
242                            sql,
243                            "REFERENCES \"{}\"(\"{}\") ON DELETE {} ON UPDATE {}",
244                            fk.table_name, fk.column_name, fk.on_delete, fk.on_update
245                        )
246                        .unwrap(),
247                        // A `character varying` carries its maximum length in
248                        // its type already, and every non string column has
249                        // nothing for `length()` to be applied to.
250                        Annotation::MaxLength(max_length) => {
251                            if matches!(column.data_type, DbType::Text) {
252                                write!(
253                                    sql,
254                                    "CONSTRAINT \"{}\" CHECK (length(\"{}\") <= {max_length})",
255                                    postgres::max_length_check_name(column.table_name, column.name),
256                                    column.name,
257                                )
258                                .unwrap();
259                            }
260                        }
261
262                        _ => {}
263                    };
264                }
265
266                Ok(())
267            }
268        }
269    }
270}
271
272/// Converts a [`DbType`] into the associated sqlite type.
273///
274/// Note, we create tables in the `STRICT` mode.
275/// Only the actual basic datatypes can be used and not their various aliases.
276pub fn sqlite_type(data_type: DbType) -> Result<&'static str, Error> {
277    #[allow(deprecated)]
278    Ok(match data_type {
279        DbType::Binary | DbType::Uuid => "BLOB",
280        DbType::VarChar
281        | DbType::Text
282        | DbType::Date
283        | DbType::DateTime
284        | DbType::Timestamp
285        | DbType::Time
286        | DbType::Choices => "TEXT",
287        DbType::Int8 | DbType::Int16 | DbType::Int32 | DbType::Int64 | DbType::Boolean => "INTEGER",
288        DbType::Float | DbType::Double => "REAL",
289        DbType::BitVec | DbType::MacAddress | DbType::IpNetwork => {
290            return Err(Error::SQLBuildError(format!(
291                "{data_type:?} is not available for sqlite"
292            )))
293        }
294    })
295}
296
297/// Return type of [`postgres_type`]
298pub enum PostgresType<'a> {
299    /// A "normal" postgres identified by a string
300    Normal(Cow<'static, str>),
301
302    /// Choices use a custom unique postgres type per column.
303    Choices(&'a [String]),
304}
305
306/// Converts a [`DbType`] into the associated postgres type.
307///
308/// Some type have to take the `annotations` into account.
309/// The `Choices` need special handling and is returned as its own enum variant.
310pub fn postgres_type<'a>(
311    data_type: DbType,
312    annotations: impl IntoIterator<Item = &'a Annotation> + Clone,
313) -> Result<PostgresType<'a>, Error> {
314    let auto_increment = annotations
315        .clone()
316        .into_iter()
317        .any(|x| matches!(x, Annotation::AutoIncrement));
318
319    let max_length = annotations.clone().into_iter().find_map(|x| match x {
320        Annotation::MaxLength(x) => Some(x),
321        _ => None,
322    });
323
324    let choices = annotations.clone().into_iter().find_map(|x| match x {
325        Annotation::Choices(x) => Some(x.as_slice()),
326        _ => None,
327    });
328
329    #[allow(deprecated)]
330    Ok(PostgresType::Normal(Cow::Borrowed(match data_type {
331        DbType::Text => "text",
332        DbType::Uuid => "uuid",
333        DbType::MacAddress => "macaddr",
334        DbType::IpNetwork => "inet",
335        DbType::BitVec => "varbit",
336        DbType::Binary => "bytea",
337        DbType::Int8 => "smallint",
338        DbType::Int16 if auto_increment => "smallserial",
339        DbType::Int16 => "smallint",
340        DbType::Int32 if auto_increment => "serial",
341        DbType::Int32 => "integer",
342        DbType::Int64 if auto_increment => "bigserial",
343        DbType::Int64 => "bigint",
344        DbType::Float => "real",
345        DbType::Double => "double precision",
346        DbType::Boolean => "boolean",
347        DbType::Date => "date",
348        DbType::DateTime => "timestamptz",
349        DbType::Timestamp => "timestamp",
350        DbType::Time => "time",
351        DbType::VarChar => {
352            return match max_length {
353                Some(x) => Ok(PostgresType::Normal(Cow::Owned(format!(
354                    "character varying ({x})"
355                )))),
356                None => Err(Error::SQLBuildError(
357                    "character varying must have a max_length annotation".to_string(),
358                )),
359            };
360        }
361        DbType::Choices => {
362            return match choices {
363                Some(x) => Ok(PostgresType::Choices(x)),
364                None => Err(Error::SQLBuildError(
365                    "VARCHAR must have a MaxLength annotation".to_string(),
366                )),
367            };
368        }
369    })))
370}