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 = "postgres")]
10use crate::db_specific::postgres;
11#[cfg(feature = "sqlite")]
12use crate::db_specific::sqlite;
13use crate::error::Error;
14use crate::{Annotation, DbType, Value};
15
16pub trait CreateColumn<'post_build>: Sized {
20 fn build(self, s: &mut String) -> Result<(), Error>;
27}
28
29#[derive(Debug)]
33pub struct SQLAnnotation<'post_build> {
34 pub(crate) annotation: &'post_build Annotation,
35}
36
37#[derive(Debug)]
41#[cfg(feature = "sqlite")]
42pub struct CreateColumnSQLiteData<'until_build, 'post_build> {
43 pub(crate) name: &'until_build str,
44 pub(crate) table_name: &'until_build str,
45 pub(crate) data_type: DbType,
46 pub(crate) annotations: Vec<SQLAnnotation<'post_build>>,
47 pub(crate) statements: Option<&'until_build mut Vec<(String, Vec<Value<'post_build>>)>>,
48 pub(crate) lookup: Option<&'until_build mut Vec<Value<'post_build>>>,
49}
50
51#[derive(Debug)]
55#[cfg(feature = "postgres")]
56pub struct CreateColumnPostgresData<'until_build, 'post_build> {
57 pub(crate) name: &'until_build str,
58 pub(crate) table_name: &'until_build str,
59 pub(crate) data_type: DbType,
60 pub(crate) annotations: Vec<SQLAnnotation<'post_build>>,
61 pub(crate) pre_statements: Option<&'until_build mut Vec<(String, Vec<Value<'post_build>>)>>,
62 pub(crate) statements: Option<&'until_build mut Vec<(String, Vec<Value<'post_build>>)>>,
63}
64
65#[derive(Debug)]
71pub enum CreateColumnImpl<'until_build, 'post_build> {
72 #[cfg(feature = "sqlite")]
76 SQLite(CreateColumnSQLiteData<'until_build, 'post_build>),
77 #[cfg(feature = "postgres")]
81 Postgres(CreateColumnPostgresData<'until_build, 'post_build>),
82}
83
84impl<'post_build> CreateColumn<'post_build> for CreateColumnImpl<'_, 'post_build> {
85 fn build(self, s: &mut String) -> Result<(), Error> {
86 match self {
87 #[cfg(feature = "sqlite")]
88 CreateColumnImpl::SQLite(mut d) => {
89 write!(
90 s,
91 "\"{}\" {} ",
92 d.name,
93 match d.data_type {
94 DbType::Binary | DbType::Uuid => "BLOB",
95 DbType::VarChar
96 | DbType::Date
97 | DbType::DateTime
98 | DbType::Timestamp
99 | DbType::Time
100 | DbType::Choices => "TEXT",
101 DbType::Int8
102 | DbType::Int16
103 | DbType::Int32
104 | DbType::Int64
105 | DbType::Boolean => "INTEGER",
106 DbType::Float | DbType::Double => "REAL",
107 DbType::BitVec | DbType::MacAddress | DbType::IpNetwork => unreachable!(
108 "BitVec, MacAddress and IpNetwork are not available for sqlite"
109 ),
110 }
111 )
112 .unwrap();
113
114 for (idx, x) in d.annotations.iter().enumerate() {
115 if let Some(ref mut s) = d.statements {
116 trigger_annotation_to_trigger_sqlite(
117 x.annotation,
118 &d.data_type,
119 d.table_name,
120 d.name,
121 s,
122 );
123 }
124
125 match &x.annotation {
126 Annotation::AutoIncrement => write!(s, "AUTOINCREMENT").unwrap(),
127 Annotation::AutoCreateTime => {
128 write!(
129 s,
130 "DEFAULT {}",
131 match d.data_type {
132 DbType::Date => "CURRENT_DATE",
133 DbType::DateTime => "CURRENT_TIMESTAMP",
134 DbType::Timestamp => "CURRENT_TIMESTAMP",
135 DbType::Time => "CURRENT_TIME",
136 _ => "",
137 }
138 )
139 .unwrap();
140 }
141 Annotation::DefaultValue(d) => match d {
142 DefaultValue::String(dv) => {
143 write!(s, "DEFAULT {}", sqlite::fmt(dv)).unwrap()
144 }
145 DefaultValue::Integer(i) => write!(s, "DEFAULT {i}").unwrap(),
146 DefaultValue::Float(f) => write!(s, "DEFAULT {f}").unwrap(),
147 DefaultValue::Boolean(b) => {
148 if *b {
149 write!(s, "DEFAULT 1").unwrap();
150 } else {
151 write!(s, "DEFAULT 0").unwrap();
152 }
153 }
154 },
155 Annotation::NotNull => write!(s, "NOT NULL").unwrap(),
156 Annotation::PrimaryKey => write!(s, "PRIMARY KEY").unwrap(),
157 Annotation::Unique => write!(s, "UNIQUE").unwrap(),
158 Annotation::ForeignKey(fk) => write!(
159 s,
160 "REFERENCES \"{}\" (\"{}\") ON DELETE {} ON UPDATE {}",
161 fk.table_name, fk.column_name, fk.on_delete, fk.on_update
162 )
163 .unwrap(),
164 _ => {}
165 }
166
167 if idx != d.annotations.len() - 1 {
168 write!(s, " ").unwrap();
169 }
170 }
171
172 Ok(())
173 }
174 #[cfg(feature = "postgres")]
175 CreateColumnImpl::Postgres(mut d) => {
176 write!(s, "\"{}\" ", d.name).unwrap();
177
178 match d.data_type {
179 DbType::VarChar => {
180 let max_length = d
181 .annotations
182 .iter()
183 .find_map(|x| match x.annotation {
184 Annotation::MaxLength(x) => Some(*x),
185 _ => None,
186 })
187 .ok_or_else(|| {
188 Error::SQLBuildError(
189 "character varying must have a max_length annotation"
190 .to_string(),
191 )
192 })?;
193
194 write!(s, "character varying ({max_length}) ").unwrap();
195 }
196 DbType::Choices => {
197 let a_opt = d
198 .annotations
199 .iter()
200 .find(|x| matches!(x.annotation, Annotation::Choices(_)));
201
202 if let Some(a) = a_opt {
203 if let Annotation::Choices(values) = a.annotation {
204 if let Some(stmts) = d.pre_statements {
205 stmts.push((
206 format!(
207 "CREATE TYPE _{}_{} AS ENUM({});",
208 d.table_name,
209 d.name,
210 values
211 .iter()
212 .map(|x| { postgres::fmt(x) })
213 .collect::<Vec<String>>()
214 .join(", ")
215 ),
216 vec![],
217 ));
218 };
219 write!(s, "_{}_{} ", d.table_name, d.name,).unwrap();
220 } else {
221 return Err(Error::SQLBuildError(
222 "VARCHAR must have a MaxLength annotation".to_string(),
223 ));
224 }
225 } else {
226 return Err(Error::SQLBuildError(
227 "VARCHAR must have a MaxLength annotation".to_string(),
228 ));
229 }
230 }
231 DbType::Uuid => write!(s, "uuid ").unwrap(),
232 DbType::MacAddress => write!(s, "macaddr ").unwrap(),
233 DbType::IpNetwork => write!(s, "inet ").unwrap(),
234 DbType::BitVec => write!(s, "varbit ").unwrap(),
235 DbType::Binary => write!(s, "bytea ").unwrap(),
236 DbType::Int8 => write!(s, "smallint ").unwrap(),
237 DbType::Int16 => {
238 if d.annotations
239 .iter()
240 .any(|x| matches!(x.annotation, Annotation::AutoIncrement))
241 {
242 write!(s, "smallserial ").unwrap();
243 } else {
244 write!(s, "smallint ").unwrap();
245 }
246 }
247 DbType::Int32 => {
248 if d.annotations
249 .iter()
250 .any(|x| matches!(x.annotation, Annotation::AutoIncrement))
251 {
252 write!(s, "serial ").unwrap();
253 } else {
254 write!(s, "integer ").unwrap();
255 }
256 }
257 DbType::Int64 => {
258 if d.annotations
259 .iter()
260 .any(|x| matches!(x.annotation, Annotation::AutoIncrement))
261 {
262 write!(s, "bigserial ").unwrap();
263 } else {
264 write!(s, "bigint ").unwrap();
265 }
266 }
267 DbType::Float => write!(s, "real ").unwrap(),
268 DbType::Double => write!(s, "double precision ").unwrap(),
269 DbType::Boolean => write!(s, "boolean ").unwrap(),
270 DbType::Date => write!(s, "date ").unwrap(),
271 DbType::DateTime => write!(s, "timestamptz ").unwrap(),
272 DbType::Timestamp => write!(s, "timestamp ").unwrap(),
273 DbType::Time => write!(s, "time ").unwrap(),
274 };
275
276 for (idx, x) in d.annotations.iter().enumerate() {
277 if let Some(ref mut s) = d.statements {
278 trigger_annotation_to_trigger_postgres(
279 x.annotation,
280 d.table_name,
281 d.name,
282 s,
283 );
284 }
285
286 match &x.annotation {
287 Annotation::AutoCreateTime => {
288 write!(
289 s,
290 "DEFAULT {}",
291 match d.data_type {
292 DbType::Date => "CURRENT_DATE",
293 DbType::DateTime => "now()",
294 DbType::Timestamp => "CURRENT_TIMESTAMP",
295 DbType::Time => "CURRENT_TIME",
296 _ => "",
297 }
298 )
299 .unwrap();
300 }
301 Annotation::DefaultValue(d) => match d {
302 DefaultValue::String(dv) => {
303 write!(s, "DEFAULT {}", postgres::fmt(dv)).unwrap()
304 }
305 DefaultValue::Integer(i) => write!(s, "DEFAULT {i}").unwrap(),
306 DefaultValue::Float(f) => write!(s, "DEFAULT {f}").unwrap(),
307 DefaultValue::Boolean(b) => {
308 if *b {
309 write!(s, "DEFAULT true").unwrap();
310 } else {
311 write!(s, "DEFAULT false").unwrap();
312 }
313 }
314 },
315 Annotation::NotNull => write!(s, "NOT NULL").unwrap(),
316 Annotation::PrimaryKey => write!(s, "PRIMARY KEY").unwrap(),
317 Annotation::Unique => write!(s, "UNIQUE").unwrap(),
318 Annotation::ForeignKey(fk) => write!(
319 s,
320 "REFERENCES \"{}\"(\"{}\") ON DELETE {} ON UPDATE {}",
321 fk.table_name, fk.column_name, fk.on_delete, fk.on_update
322 )
323 .unwrap(),
324 _ => {}
325 };
326
327 if idx != d.annotations.len() - 1 {
328 write!(s, " ").unwrap();
329 }
330 }
331
332 Ok(())
333 }
334 }
335 }
336}