1use super::Dialect;
2use super::column::{Column, ColumnType};
3use crate::query::validate;
4use crate::{Driver, Error};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Action {
9 Cascade,
10 Restrict,
11 SetNull,
12 SetDefault,
13 NoAction,
14}
15
16impl Action {
17 fn sql(self) -> &'static str {
18 match self {
19 Self::Cascade => "CASCADE",
20 Self::Restrict => "RESTRICT",
21 Self::SetNull => "SET NULL",
22 Self::SetDefault => "SET DEFAULT",
23 Self::NoAction => "NO ACTION",
24 }
25 }
26}
27
28#[derive(Debug, Clone)]
30pub struct ForeignKey {
31 column: String,
32 name: Option<String>,
33 target: Option<(String, String)>,
34 on_delete: Option<Action>,
35 on_update: Option<Action>,
36}
37
38impl ForeignKey {
39 pub fn references(&mut self, table: &str, column: &str) -> &mut Self {
41 self.target = Some((table.to_owned(), column.to_owned()));
42 self
43 }
44
45 pub fn on_delete(&mut self, action: Action) -> &mut Self {
47 self.on_delete = Some(action);
48 self
49 }
50
51 pub fn on_update(&mut self, action: Action) -> &mut Self {
53 self.on_update = Some(action);
54 self
55 }
56
57 pub fn name(&mut self, name: &str) -> &mut Self {
60 self.name = Some(name.to_owned());
61 self
62 }
63}
64
65#[derive(Debug, Clone)]
67pub struct Index {
68 columns: Vec<String>,
69 unique: bool,
70 name: Option<String>,
71}
72
73impl Index {
74 pub fn name(&mut self, name: &str) -> &mut Self {
78 self.name = Some(name.to_owned());
79 self
80 }
81
82 fn name_for(&self, table: &str) -> Result<String, Error> {
84 match &self.name {
85 Some(name) => {
86 validate(name)?;
87 Ok(name.clone())
88 }
89 None => Ok(name_of(
90 table,
91 &self.columns,
92 if self.unique { "unique" } else { "index" },
93 )),
94 }
95 }
96}
97
98#[derive(Debug, Clone)]
103enum Command {
104 Column(usize),
105 Index(Index),
106 Primary { columns: Vec<String> },
107 Foreign(ForeignKey),
108 RenameColumn { from: String, to: String },
109 DropColumn { name: String },
110 DropIndex { name: String },
111 DropForeign { name: String },
112}
113
114#[derive(Debug, Clone, Default)]
117pub struct Blueprint {
118 columns: Vec<Column>,
119 commands: Vec<Command>,
120}
121
122impl Blueprint {
123 pub(crate) fn new() -> Self {
124 Self::default()
125 }
126
127 fn add(&mut self, name: &str, ty: ColumnType) -> &mut Column {
128 self.commands.push(Command::Column(self.columns.len()));
129 self.columns.push(Column::new(name, ty));
130 self.columns.last_mut().expect("just pushed")
131 }
132
133 pub fn id(&mut self) -> &mut Column {
136 self.add("id", ColumnType::Id)
137 }
138
139 pub fn string(&mut self, name: &str) -> &mut Column {
141 self.add(name, ColumnType::String(255))
142 }
143
144 pub fn text(&mut self, name: &str) -> &mut Column {
146 self.add(name, ColumnType::Text)
147 }
148
149 pub fn integer(&mut self, name: &str) -> &mut Column {
151 self.add(name, ColumnType::Integer)
152 }
153
154 pub fn big_integer(&mut self, name: &str) -> &mut Column {
156 self.add(name, ColumnType::BigInteger)
157 }
158
159 pub fn boolean(&mut self, name: &str) -> &mut Column {
161 self.add(name, ColumnType::Boolean)
162 }
163
164 pub fn date_time(&mut self, name: &str) -> &mut Column {
168 self.add(name, ColumnType::DateTime(0))
169 }
170
171 pub fn timestamp(&mut self, name: &str) -> &mut Column {
180 self.add(name, ColumnType::Timestamp(0))
181 }
182
183 pub fn binary(&mut self, name: &str) -> &mut Column {
185 self.add(name, ColumnType::Binary)
186 }
187
188 pub fn timestamps(&mut self) {
190 self.date_time("created_at").nullable();
191 self.date_time("updated_at").nullable();
192 }
193
194 pub fn unique<I, S>(&mut self, columns: I) -> &mut Index
197 where
198 I: IntoIterator<Item = S>,
199 S: AsRef<str>,
200 {
201 self.add_index(columns, true)
202 }
203
204 pub fn index<I, S>(&mut self, columns: I) -> &mut Index
207 where
208 I: IntoIterator<Item = S>,
209 S: AsRef<str>,
210 {
211 self.add_index(columns, false)
212 }
213
214 fn add_index<I, S>(&mut self, columns: I, unique: bool) -> &mut Index
215 where
216 I: IntoIterator<Item = S>,
217 S: AsRef<str>,
218 {
219 self.commands.push(Command::Index(Index {
220 columns: collect(columns),
221 unique,
222 name: None,
223 }));
224 match self.commands.last_mut() {
225 Some(Command::Index(index)) => index,
226 _ => unreachable!("just pushed an index"),
227 }
228 }
229
230 pub fn primary<I, S>(&mut self, columns: I)
235 where
236 I: IntoIterator<Item = S>,
237 S: AsRef<str>,
238 {
239 self.commands.push(Command::Primary {
240 columns: collect(columns),
241 });
242 }
243
244 pub fn foreign(&mut self, column: &str) -> &mut ForeignKey {
248 self.commands.push(Command::Foreign(ForeignKey {
249 column: column.to_owned(),
250 name: None,
251 target: None,
252 on_delete: None,
253 on_update: None,
254 }));
255 match self.commands.last_mut() {
256 Some(Command::Foreign(fk)) => fk,
257 _ => unreachable!("just pushed a foreign key"),
258 }
259 }
260
261 pub fn rename_column(&mut self, from: &str, to: &str) {
268 self.commands.push(Command::RenameColumn {
269 from: from.to_owned(),
270 to: to.to_owned(),
271 });
272 }
273
274 pub fn drop_column(&mut self, name: &str) {
277 self.commands.push(Command::DropColumn {
278 name: name.to_owned(),
279 });
280 }
281
282 pub fn drop_index(&mut self, name: &str) {
291 self.commands.push(Command::DropIndex {
292 name: name.to_owned(),
293 });
294 }
295
296 pub fn drop_foreign(&mut self, name: &str) {
300 self.commands.push(Command::DropForeign {
301 name: name.to_owned(),
302 });
303 }
304
305 pub(crate) fn render_create(
308 &self,
309 driver: Driver,
310 dialect: Dialect,
311 table: &str,
312 if_not_exists: bool,
313 ) -> Result<Vec<String>, Error> {
314 validate(table)?;
315 let unsupported = |operation| Error::Unsupported {
316 operation,
317 driver: driver.name(),
318 };
319 let q = |name: &str| driver.quote(name);
320 let exists = if if_not_exists { "IF NOT EXISTS " } else { "" };
321
322 let mut parts = Vec::new();
323 for column in &self.columns {
324 if column.change {
325 return Err(unsupported("changing a column in create"));
326 }
327 parts.push(column.render(driver, dialect)?);
328 }
329 let mut after = Vec::new();
330 for command in &self.commands {
331 match command {
332 Command::Column(_) => {}
333 Command::Index(index) => {
334 let columns = column_list(driver, &index.columns)?;
335 let name = index.name_for(table)?;
336 if index.unique {
337 parts.push(format!("CONSTRAINT {} UNIQUE ({columns})", q(&name)));
338 } else {
339 match dialect {
340 Dialect::MySql => parts.push(format!("INDEX {} ({columns})", q(&name))),
341 _ => after.push(format!(
342 "CREATE INDEX {exists}{} ON {} ({columns})",
343 q(&name),
344 q(table)
345 )),
346 }
347 }
348 }
349 Command::Primary { columns } => {
350 parts.push(format!("PRIMARY KEY ({})", column_list(driver, columns)?));
351 }
352 Command::Foreign(_) => {}
353 Command::RenameColumn { .. } => {
354 return Err(unsupported("renaming a column in create"));
355 }
356 Command::DropColumn { .. } => {
357 return Err(unsupported("dropping a column in create"));
358 }
359 Command::DropIndex { .. } => {
360 return Err(unsupported("dropping an index in create"));
361 }
362 Command::DropForeign { .. } => {
363 return Err(unsupported("dropping a foreign key in create"));
364 }
365 }
366 }
367 for command in &self.commands {
369 if let Command::Foreign(fk) = command {
370 parts.push(fk.render(driver, table)?);
371 }
372 }
373 let mut out = vec![format!(
374 "CREATE TABLE {exists}{} ({})",
375 q(table),
376 parts.join(", ")
377 )];
378 out.extend(after);
379 Ok(out)
380 }
381
382 pub(crate) fn render_alter(
386 &self,
387 driver: Driver,
388 dialect: Dialect,
389 table: &str,
390 ) -> Result<Vec<String>, Error> {
391 validate(table)?;
392 let unsupported = |operation| Error::Unsupported {
393 operation,
394 driver: driver.name(),
395 };
396 let q = |name: &str| driver.quote(name);
397 let alter = format!("ALTER TABLE {}", q(table));
398
399 let mut out = Vec::new();
400 for command in &self.commands {
401 match command {
402 Command::Column(i) => {
403 let column = &self.columns[*i];
404 if column.change {
405 if dialect == Dialect::Sqlite {
406 return Err(unsupported("changing a column"));
407 }
408 if column.ty == ColumnType::Id {
409 return Err(unsupported("changing an id column"));
410 }
411 if column.primary {
412 return Err(unsupported("changing a column into a primary key"));
413 }
414 out.push(match dialect {
415 Dialect::Postgres => {
416 format!("{alter} {}", column.render_change_postgres(driver)?)
417 }
418 _ => {
419 format!("{alter} MODIFY COLUMN {}", column.render(driver, dialect)?)
420 }
421 });
422 } else {
423 if (column.ty == ColumnType::Id || column.primary)
424 && dialect == Dialect::Sqlite
425 {
426 return Err(unsupported("adding a primary key column"));
427 }
428 out.push(format!(
429 "{alter} ADD COLUMN {}",
430 column.render(driver, dialect)?
431 ));
432 }
433 }
434 Command::Index(index) => {
435 let columns = column_list(driver, &index.columns)?;
436 let name = q(&index.name_for(table)?);
437 out.push(match (dialect, index.unique) {
438 (Dialect::MySql, false) => format!("{alter} ADD INDEX {name} ({columns})"),
439 (Dialect::MySql, true) => format!("{alter} ADD UNIQUE {name} ({columns})"),
440 (Dialect::Postgres, true) => {
441 format!("{alter} ADD CONSTRAINT {name} UNIQUE ({columns})")
442 }
443 (Dialect::Sqlite, true) => {
444 format!("CREATE UNIQUE INDEX {name} ON {} ({columns})", q(table))
445 }
446 (_, false) => format!("CREATE INDEX {name} ON {} ({columns})", q(table)),
447 });
448 }
449 Command::Primary { columns } => {
450 let columns = column_list(driver, columns)?;
451 if dialect == Dialect::Sqlite {
452 return Err(unsupported("adding a primary key"));
453 }
454 out.push(format!("{alter} ADD PRIMARY KEY ({columns})"));
455 }
456 Command::Foreign(fk) => {
457 let constraint = fk.render(driver, table)?;
459 if dialect != Dialect::Sqlite {
460 out.push(format!("{alter} ADD {constraint}"));
461 }
462 }
463 Command::RenameColumn { from, to } => {
464 validate(from)?;
465 validate(to)?;
466 out.push(format!("{alter} RENAME COLUMN {} TO {}", q(from), q(to)));
467 }
468 Command::DropColumn { name } => {
469 validate(name)?;
470 out.push(format!("{alter} DROP COLUMN {}", q(name)));
471 }
472 Command::DropIndex { name } => {
473 validate(name)?;
474 out.push(match dialect {
475 Dialect::MySql => format!("{alter} DROP INDEX {}", q(name)),
476 _ => format!("DROP INDEX {}", q(name)),
477 });
478 }
479 Command::DropForeign { name } => {
480 validate(name)?;
481 match dialect {
482 Dialect::MySql => out.push(format!("{alter} DROP FOREIGN KEY {}", q(name))),
483 Dialect::Postgres => {
484 out.push(format!("{alter} DROP CONSTRAINT {}", q(name)))
485 }
486 Dialect::Sqlite => {}
487 }
488 }
489 }
490 }
491 Ok(out)
492 }
493}
494
495impl ForeignKey {
496 fn render(&self, driver: Driver, table: &str) -> Result<String, Error> {
499 let q = |name: &str| driver.quote(name);
500 validate(&self.column)?;
501 let Some((ref_table, ref_column)) = &self.target else {
502 return Err(Error::Unsupported {
503 operation: "a foreign key without references()",
504 driver: driver.name(),
505 });
506 };
507 validate(ref_table)?;
508 validate(ref_column)?;
509 let name = match &self.name {
510 Some(name) => {
511 validate(name)?;
512 name.clone()
513 }
514 None => name_of(table, std::slice::from_ref(&self.column), "foreign"),
515 };
516 let mut sql = format!(
517 "CONSTRAINT {} FOREIGN KEY ({}) REFERENCES {} ({})",
518 q(&name),
519 q(&self.column),
520 q(ref_table),
521 q(ref_column)
522 );
523 if let Some(a) = self.on_delete {
524 sql.push_str(&format!(" ON DELETE {}", a.sql()));
525 }
526 if let Some(a) = self.on_update {
527 sql.push_str(&format!(" ON UPDATE {}", a.sql()));
528 }
529 Ok(sql)
530 }
531}
532
533fn name_of(table: &str, columns: &[String], suffix: &str) -> String {
535 format!("{table}_{}_{suffix}", columns.join("_"))
536}
537
538fn column_list(driver: Driver, columns: &[String]) -> Result<String, Error> {
540 for c in columns {
541 validate(c)?;
542 }
543 Ok(columns
544 .iter()
545 .map(|c| driver.quote(c))
546 .collect::<Vec<_>>()
547 .join(", "))
548}
549
550fn collect<I, S>(columns: I) -> Vec<String>
551where
552 I: IntoIterator<Item = S>,
553 S: AsRef<str>,
554{
555 columns.into_iter().map(|c| c.as_ref().to_owned()).collect()
556}