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
17pub trait CreateColumn<'post_build>: Sized {
21 fn build(self, s: &mut String) -> Result<(), Error>;
28}
29
30#[derive(Debug)]
34pub struct SQLAnnotation<'post_build> {
35 pub(crate) annotation: &'post_build Annotation,
36}
37
38#[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#[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#[derive(Debug)]
72pub enum CreateColumnImpl<'until_build, 'post_build> {
73 #[cfg(feature = "sqlite")]
77 SQLite(CreateColumnSQLiteData<'until_build, 'post_build>),
78 #[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 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
272pub 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
297pub enum PostgresType<'a> {
299 Normal(Cow<'static, str>),
301
302 Choices(&'a [String]),
304}
305
306pub 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}