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 medium_text(&mut self, name: &str) -> &mut Column {
153 self.add(name, ColumnType::MediumText)
154 }
155
156 pub fn long_text(&mut self, name: &str) -> &mut Column {
161 self.add(name, ColumnType::LongText)
162 }
163
164 pub fn integer(&mut self, name: &str) -> &mut Column {
166 self.add(name, ColumnType::Integer)
167 }
168
169 pub fn big_integer(&mut self, name: &str) -> &mut Column {
171 self.add(name, ColumnType::BigInteger)
172 }
173
174 pub fn boolean(&mut self, name: &str) -> &mut Column {
176 self.add(name, ColumnType::Boolean)
177 }
178
179 pub fn date_time(&mut self, name: &str) -> &mut Column {
183 self.add(name, ColumnType::DateTime(0))
184 }
185
186 pub fn timestamp(&mut self, name: &str) -> &mut Column {
195 self.add(name, ColumnType::Timestamp(0))
196 }
197
198 pub fn binary(&mut self, name: &str) -> &mut Column {
200 self.add(name, ColumnType::Binary)
201 }
202
203 pub fn timestamps(&mut self) {
205 self.date_time("created_at").nullable();
206 self.date_time("updated_at").nullable();
207 }
208
209 pub fn unique<I, S>(&mut self, columns: I) -> &mut Index
212 where
213 I: IntoIterator<Item = S>,
214 S: AsRef<str>,
215 {
216 self.add_index(columns, true)
217 }
218
219 pub fn index<I, S>(&mut self, columns: I) -> &mut Index
222 where
223 I: IntoIterator<Item = S>,
224 S: AsRef<str>,
225 {
226 self.add_index(columns, false)
227 }
228
229 fn add_index<I, S>(&mut self, columns: I, unique: bool) -> &mut Index
230 where
231 I: IntoIterator<Item = S>,
232 S: AsRef<str>,
233 {
234 self.commands.push(Command::Index(Index {
235 columns: collect(columns),
236 unique,
237 name: None,
238 }));
239 match self.commands.last_mut() {
240 Some(Command::Index(index)) => index,
241 _ => unreachable!("just pushed an index"),
242 }
243 }
244
245 pub fn primary<I, S>(&mut self, columns: I)
250 where
251 I: IntoIterator<Item = S>,
252 S: AsRef<str>,
253 {
254 self.commands.push(Command::Primary {
255 columns: collect(columns),
256 });
257 }
258
259 pub fn foreign(&mut self, column: &str) -> &mut ForeignKey {
263 self.commands.push(Command::Foreign(ForeignKey {
264 column: column.to_owned(),
265 name: None,
266 target: None,
267 on_delete: None,
268 on_update: None,
269 }));
270 match self.commands.last_mut() {
271 Some(Command::Foreign(fk)) => fk,
272 _ => unreachable!("just pushed a foreign key"),
273 }
274 }
275
276 pub fn rename_column(&mut self, from: &str, to: &str) {
283 self.commands.push(Command::RenameColumn {
284 from: from.to_owned(),
285 to: to.to_owned(),
286 });
287 }
288
289 pub fn drop_column(&mut self, name: &str) {
292 self.commands.push(Command::DropColumn {
293 name: name.to_owned(),
294 });
295 }
296
297 pub fn drop_index(&mut self, name: &str) {
306 self.commands.push(Command::DropIndex {
307 name: name.to_owned(),
308 });
309 }
310
311 pub fn drop_foreign(&mut self, name: &str) {
315 self.commands.push(Command::DropForeign {
316 name: name.to_owned(),
317 });
318 }
319
320 pub(crate) fn render_create(
323 &self,
324 driver: Driver,
325 dialect: Dialect,
326 table: &str,
327 if_not_exists: bool,
328 ) -> Result<Vec<String>, Error> {
329 validate(table)?;
330 let unsupported = |operation| Error::Unsupported {
331 operation,
332 driver: driver.name(),
333 };
334 let q = |name: &str| driver.quote(name);
335 let exists = if if_not_exists { "IF NOT EXISTS " } else { "" };
336
337 let mut parts = Vec::new();
338 for column in &self.columns {
339 if column.change {
340 return Err(unsupported("changing a column in create"));
341 }
342 parts.push(column.render(driver, dialect)?);
343 }
344 let mut after = Vec::new();
345 for command in &self.commands {
346 match command {
347 Command::Column(_) => {}
348 Command::Index(index) => {
349 let columns = column_list(driver, &index.columns)?;
350 let name = index.name_for(table)?;
351 if index.unique {
352 parts.push(format!("CONSTRAINT {} UNIQUE ({columns})", q(&name)));
353 } else {
354 match dialect {
355 Dialect::MySql => parts.push(format!("INDEX {} ({columns})", q(&name))),
356 _ => after.push(format!(
357 "CREATE INDEX {exists}{} ON {} ({columns})",
358 q(&name),
359 q(table)
360 )),
361 }
362 }
363 }
364 Command::Primary { columns } => {
365 parts.push(format!("PRIMARY KEY ({})", column_list(driver, columns)?));
366 }
367 Command::Foreign(_) => {}
368 Command::RenameColumn { .. } => {
369 return Err(unsupported("renaming a column in create"));
370 }
371 Command::DropColumn { .. } => {
372 return Err(unsupported("dropping a column in create"));
373 }
374 Command::DropIndex { .. } => {
375 return Err(unsupported("dropping an index in create"));
376 }
377 Command::DropForeign { .. } => {
378 return Err(unsupported("dropping a foreign key in create"));
379 }
380 }
381 }
382 for command in &self.commands {
384 if let Command::Foreign(fk) = command {
385 parts.push(fk.render(driver, table)?);
386 }
387 }
388 let mut out = vec![format!(
389 "CREATE TABLE {exists}{} ({})",
390 q(table),
391 parts.join(", ")
392 )];
393 out.extend(after);
394 Ok(out)
395 }
396
397 pub(crate) fn render_alter(
401 &self,
402 driver: Driver,
403 dialect: Dialect,
404 table: &str,
405 ) -> Result<Vec<String>, Error> {
406 validate(table)?;
407 let unsupported = |operation| Error::Unsupported {
408 operation,
409 driver: driver.name(),
410 };
411 let q = |name: &str| driver.quote(name);
412 let alter = format!("ALTER TABLE {}", q(table));
413
414 let mut out = Vec::new();
415 for command in &self.commands {
416 match command {
417 Command::Column(i) => {
418 let column = &self.columns[*i];
419 if column.change {
420 if dialect == Dialect::Sqlite {
421 return Err(unsupported("changing a column"));
422 }
423 if column.ty == ColumnType::Id {
424 return Err(unsupported("changing an id column"));
425 }
426 if column.primary {
427 return Err(unsupported("changing a column into a primary key"));
428 }
429 out.push(match dialect {
430 Dialect::Postgres => {
431 format!("{alter} {}", column.render_change_postgres(driver)?)
432 }
433 _ => {
434 format!("{alter} MODIFY COLUMN {}", column.render(driver, dialect)?)
435 }
436 });
437 } else {
438 if (column.ty == ColumnType::Id || column.primary)
439 && dialect == Dialect::Sqlite
440 {
441 return Err(unsupported("adding a primary key column"));
442 }
443 out.push(format!(
444 "{alter} ADD COLUMN {}",
445 column.render(driver, dialect)?
446 ));
447 }
448 }
449 Command::Index(index) => {
450 let columns = column_list(driver, &index.columns)?;
451 let name = q(&index.name_for(table)?);
452 out.push(match (dialect, index.unique) {
453 (Dialect::MySql, false) => format!("{alter} ADD INDEX {name} ({columns})"),
454 (Dialect::MySql, true) => format!("{alter} ADD UNIQUE {name} ({columns})"),
455 (Dialect::Postgres, true) => {
456 format!("{alter} ADD CONSTRAINT {name} UNIQUE ({columns})")
457 }
458 (Dialect::Sqlite, true) => {
459 format!("CREATE UNIQUE INDEX {name} ON {} ({columns})", q(table))
460 }
461 (_, false) => format!("CREATE INDEX {name} ON {} ({columns})", q(table)),
462 });
463 }
464 Command::Primary { columns } => {
465 let columns = column_list(driver, columns)?;
466 if dialect == Dialect::Sqlite {
467 return Err(unsupported("adding a primary key"));
468 }
469 out.push(format!("{alter} ADD PRIMARY KEY ({columns})"));
470 }
471 Command::Foreign(fk) => {
472 let constraint = fk.render(driver, table)?;
474 if dialect != Dialect::Sqlite {
475 out.push(format!("{alter} ADD {constraint}"));
476 }
477 }
478 Command::RenameColumn { from, to } => {
479 validate(from)?;
480 validate(to)?;
481 out.push(format!("{alter} RENAME COLUMN {} TO {}", q(from), q(to)));
482 }
483 Command::DropColumn { name } => {
484 validate(name)?;
485 out.push(format!("{alter} DROP COLUMN {}", q(name)));
486 }
487 Command::DropIndex { name } => {
488 validate(name)?;
489 out.push(match dialect {
490 Dialect::MySql => format!("{alter} DROP INDEX {}", q(name)),
491 _ => format!("DROP INDEX {}", q(name)),
492 });
493 }
494 Command::DropForeign { name } => {
495 validate(name)?;
496 match dialect {
497 Dialect::MySql => out.push(format!("{alter} DROP FOREIGN KEY {}", q(name))),
498 Dialect::Postgres => {
499 out.push(format!("{alter} DROP CONSTRAINT {}", q(name)))
500 }
501 Dialect::Sqlite => {}
502 }
503 }
504 }
505 }
506 Ok(out)
507 }
508}
509
510impl ForeignKey {
511 fn render(&self, driver: Driver, table: &str) -> Result<String, Error> {
514 let q = |name: &str| driver.quote(name);
515 validate(&self.column)?;
516 let Some((ref_table, ref_column)) = &self.target else {
517 return Err(Error::Unsupported {
518 operation: "a foreign key without references()",
519 driver: driver.name(),
520 });
521 };
522 validate(ref_table)?;
523 validate(ref_column)?;
524 let name = match &self.name {
525 Some(name) => {
526 validate(name)?;
527 name.clone()
528 }
529 None => name_of(table, std::slice::from_ref(&self.column), "foreign"),
530 };
531 let mut sql = format!(
532 "CONSTRAINT {} FOREIGN KEY ({}) REFERENCES {} ({})",
533 q(&name),
534 q(&self.column),
535 q(ref_table),
536 q(ref_column)
537 );
538 if let Some(a) = self.on_delete {
539 sql.push_str(&format!(" ON DELETE {}", a.sql()));
540 }
541 if let Some(a) = self.on_update {
542 sql.push_str(&format!(" ON UPDATE {}", a.sql()));
543 }
544 Ok(sql)
545 }
546}
547
548fn name_of(table: &str, columns: &[String], suffix: &str) -> String {
550 format!("{table}_{}_{suffix}", columns.join("_"))
551}
552
553fn column_list(driver: Driver, columns: &[String]) -> Result<String, Error> {
555 for c in columns {
556 validate(c)?;
557 }
558 Ok(columns
559 .iter()
560 .map(|c| driver.quote(c))
561 .collect::<Vec<_>>()
562 .join(", "))
563}
564
565fn collect<I, S>(columns: I) -> Vec<String>
566where
567 I: IntoIterator<Item = S>,
568 S: AsRef<str>,
569{
570 columns.into_iter().map(|c| c.as_ref().to_owned()).collect()
571}