Commit 3879a3c5 authored by Julius de Bruijn's avatar Julius de Bruijn Committed by Katharina Fey
Browse files

Allow adding foreign keys

parent 31cf039d
......@@ -96,4 +96,12 @@ pub trait SqlGenerator {
/// Drop a multi-column index
fn drop_index(name: &str) -> String;
/// Add a foreign key
fn add_foreign_key(
columns: &[String],
table: &str,
relation_columns: &[String],
schema: Option<&str>,
) -> String;
}
......@@ -152,6 +152,27 @@ impl SqlGenerator for MsSql {
fn drop_index(name: &str) -> String {
format!("DROP INDEX [{}]", name)
}
fn add_foreign_key(
columns: &[String],
table: &str,
relation_columns: &[String],
schema: Option<&str>,
) -> String {
let columns: Vec<_> = columns.into_iter().map(|c| format!("[{}]", c)).collect();
let relation_columns: Vec<_> = relation_columns
.into_iter()
.map(|c| format!("[{}]", c))
.collect();
format!(
"FOREIGN KEY({}) REFERENCES {}[{}]({})",
columns.join(","),
prefix!(schema),
table,
relation_columns.join(","),
)
}
}
impl MsSql {
......
......@@ -120,6 +120,27 @@ impl SqlGenerator for MySql {
fn drop_index(name: &str) -> String {
format!("DROP INDEX `{}`", name)
}
fn add_foreign_key(
columns: &[String],
table: &str,
relation_columns: &[String],
schema: Option<&str>,
) -> String {
let columns: Vec<_> = columns.into_iter().map(|c| format!("`{}`", c)).collect();
let relation_columns: Vec<_> = relation_columns
.into_iter()
.map(|c| format!("`{}`", c))
.collect();
format!(
"FOREIGN KEY({}) REFERENCES {}`{}`({})",
columns.join(","),
prefix!(schema),
table,
relation_columns.join(","),
)
}
}
impl MySql {
......
......@@ -122,6 +122,28 @@ impl SqlGenerator for Pg {
fn drop_index(name: &str) -> String {
format!("DROP INDEX \"{}\"", name)
}
fn add_foreign_key(
columns: &[String],
table: &str,
relation_columns: &[String],
schema: Option<&str>,
) -> String {
let columns: Vec<_> = columns.into_iter().map(|c| format!("\"{}\"", c)).collect();
let relation_columns: Vec<_> = relation_columns
.into_iter()
.map(|c| format!("\"{}\"", c))
.collect();
format!(
"FOREIGN KEY({}) REFERENCES {}\"{}\"({})",
columns.join(","),
prefix!(schema),
table,
relation_columns.join(","),
)
}
}
impl Pg {
......
......@@ -119,6 +119,27 @@ impl SqlGenerator for Sqlite {
fn rename_column(_: &str, _: &str) -> String {
panic!("Sqlite does not support renaming columns!")
}
fn add_foreign_key(
columns: &[String],
table: &str,
relation_columns: &[String],
_: Option<&str>,
) -> String {
let columns: Vec<_> = columns.into_iter().map(|c| format!("\"{}\"", c)).collect();
let relation_columns: Vec<_> = relation_columns
.into_iter()
.map(|c| format!("\"{}\"", c))
.collect();
format!(
"FOREIGN KEY({}) REFERENCES \"{}\"({})",
columns.join(","),
table,
relation_columns.join(","),
)
}
}
impl Sqlite {
......
......@@ -174,3 +174,14 @@ pub enum IndexChange {
/// Remove a multi-column index
RemoveIndex(String, String),
}
/// An enum set that represents operations done with and on foreign keys
#[derive(Clone)]
pub enum ForeignKeyChange {
/// Add a foreign key
AddForeignKey {
columns: Vec<String>,
table: String,
relation_columns: Vec<String>,
},
}
......@@ -60,7 +60,7 @@ impl Migration {
&mut CreateTable(ref mut t, ref mut cb)
| &mut CreateTableIfNotExists(ref mut t, ref mut cb) => {
cb(t); // Run the user code
let (cols, indices) = t.make::<T>(false, schema);
let (cols, indices, foreign_keys) = t.make::<T>(false, schema);
let name = t.meta.name().clone();
sql.push_str(&match change {
......@@ -79,6 +79,20 @@ impl Migration {
sql.push_str(", ");
}
}
let l = foreign_keys.len();
for (i, slice) in foreign_keys.iter().enumerate() {
if cols.len() > 0 && i == 0 {
sql.push_str(", ")
}
sql.push_str(slice);
if i < l - 1 {
sql.push_str(", ")
}
}
sql.push_str(")");
// Add additional index columns
......@@ -96,9 +110,11 @@ impl Migration {
}
&mut ChangeTable(ref mut t, ref mut cb) => {
cb(t);
let (cols, indices) = t.make::<T>(true, schema);
let (cols, indices, fks) = t.make::<T>(true, schema);
sql.push_str(&T::alter_table(&t.meta.name(), schema));
sql.push_str(" ");
let l = cols.len();
for (i, slice) in cols.iter().enumerate() {
sql.push_str(slice);
......@@ -108,6 +124,20 @@ impl Migration {
}
}
let l = fks.len();
for (i, slice) in fks.iter().enumerate() {
if cols.len() > 0 && i == 0 {
sql.push_str(", ")
}
sql.push_str("ADD ");
sql.push_str(slice);
if i < l - 1 {
sql.push_str(", ")
}
}
// Add additional index columns
if indices.len() > 0 {
sql.push_str(";");
......
......@@ -7,8 +7,7 @@
//! You can also change existing tables with a closure that can
//! then access individual columns in that table.
use super::backend::SqlGenerator;
use super::{IndexChange, TableChange};
use super::{backend::SqlGenerator, ForeignKeyChange, IndexChange, TableChange};
use crate::types::Type;
use std::fmt::{Debug, Formatter, Result as FmtResult};
......@@ -24,11 +23,18 @@ impl Debug for IndexChange {
}
}
impl Debug for ForeignKeyChange {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.write_str("ForeignKeyChange")
}
}
#[derive(Debug, Clone)]
pub struct Table {
pub meta: TableMeta,
columns: Vec<TableChange>,
indices: Vec<IndexChange>,
foreign_keys: Vec<ForeignKeyChange>,
}
impl Table {
......@@ -37,6 +43,7 @@ impl Table {
meta: TableMeta::new(name.into()),
columns: vec![],
indices: vec![],
foreign_keys: vec![],
}
}
......@@ -103,18 +110,45 @@ impl Table {
));
}
/// Generate Sql for this table, returned as two vectors
pub fn add_foreign_key(
&mut self,
columns_on_this_side: &[&str],
related_table: &str,
columns_on_that_side: &[&str],
) {
let table = related_table.into();
let columns = columns_on_this_side
.into_iter()
.map(|c| String::from(*c))
.collect();
let relation_columns = columns_on_that_side
.into_iter()
.map(|c| String::from(*c))
.collect();
self.foreign_keys.push(ForeignKeyChange::AddForeignKey {
table,
columns,
relation_columns,
})
}
/// Generate Sql for this table, returned as three vectors
///
/// The first vector (`.0`) represents all column changes done to the table,
/// the second vector (`.1`) contains all index and suffix changes.
/// the third vector (`.2`) contains all foreign key changes.
///
/// It is very well possible for either of them to be empty,
/// It is very well possible for all of them to be empty,
/// although both being empty *might* signify an error.
pub fn make<T: SqlGenerator>(
&mut self,
ex: bool,
schema: Option<&str>,
) -> (Vec<String>, Vec<String>) {
) -> (Vec<String>, Vec<String>, Vec<String>) {
use ForeignKeyChange as KFC;
use IndexChange as IC;
use TableChange as TC;
......@@ -143,7 +177,24 @@ impl Table {
})
.collect();
(columns, indeces)
let foreign_keys = self
.foreign_keys
.iter()
.map(|change| match change {
KFC::AddForeignKey {
columns,
table,
relation_columns,
} => T::add_foreign_key(
columns.as_slice(),
table,
relation_columns.as_slice(),
schema,
),
})
.collect();
(columns, indeces, foreign_keys)
}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment