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

Indices, more types and so on

parent bfed70be
......@@ -94,6 +94,21 @@ pub trait SqlGenerator {
/// Create a multi-column index
fn create_index(table: &str, schema: Option<&str>, name: &str, _type: &Type) -> String;
/// Create a multi-column index
fn create_partial_index(
table: &str,
schema: Option<&str>,
name: &str,
_type: &Type,
conditions: &str,
) -> String {
format!(
"{} WHERE {}",
Self::create_index(table, schema, name, _type),
conditions
)
}
/// Drop a multi-column index
fn drop_index(name: &str) -> String;
......@@ -104,4 +119,6 @@ pub trait SqlGenerator {
relation_columns: &[String],
schema: Option<&str>,
) -> String;
fn add_primary_key(columns: &[String]) -> String;
}
......@@ -4,7 +4,10 @@
//! databases. They should be thoroughly tested via unit testing
use super::SqlGenerator;
use crate::types::{BaseType, Type};
use crate::{
functions::AutogenFunction,
types::{BaseType, Type, WrappedDefault},
};
/// A simple macro that will generate a quoted schema prefix if it exists
macro_rules! quoted_prefix {
......@@ -26,6 +29,24 @@ macro_rules! prefix {
/// SQL Server generator backend
pub struct MsSql;
impl MsSql {
fn default(default: &WrappedDefault<'static>) -> String {
match default {
WrappedDefault::Function(ref fun) => match fun {
AutogenFunction::CurrentTimestamp => format!(" DEFAULT CURRENT_TIMESTAMP"),
},
WrappedDefault::Null => format!(" DEFAULT NULL"),
WrappedDefault::AnyText(ref val) => format!(" DEFAULT '{}'", val),
WrappedDefault::UUID(ref val) => format!(" DEFAULT '{}'", val),
WrappedDefault::Date(ref val) => format!(" DEFAULT '{:?}'", val),
WrappedDefault::Boolean(val) => format!(" DEFAULT {}", if *val { 1 } else { 0 }),
WrappedDefault::Custom(ref val) => format!(" DEFAULT '{}'", val),
_ => format!(" DEFAULT {}", default),
}
}
}
impl SqlGenerator for MsSql {
fn create_table(name: &str, schema: Option<&str>) -> String {
format!("CREATE TABLE {}[{}]", quoted_prefix!(schema), name)
......@@ -90,7 +111,7 @@ impl SqlGenerator for MsSql {
};
let default_indicator = match (&tt.default).as_ref() {
Some(ref m) => format!(" DEFAULT '{}'", m),
Some(ref m) => Self::default(m),
_ => format!(""),
};
......@@ -173,6 +194,11 @@ impl SqlGenerator for MsSql {
relation_columns.join(","),
)
}
fn add_primary_key(columns: &[String]) -> String {
let columns: Vec<_> = columns.into_iter().map(|c| format!("[{}]", c)).collect();
format!("PRIMARY KEY ({})", columns.join(","))
}
}
impl MsSql {
......@@ -191,9 +217,11 @@ impl MsSql {
0 => format!("VARCHAR(MAX)"), // For "0" remove the limit
_ => format!("VARCHAR({})", l),
},
Char(l) => format!("CHAR({})", l),
/* "NOT NULL" is added here because normally primary keys are implicitly not-null */
Primary => format!("INT IDENTITY(1,1) PRIMARY KEY NOT NULL"),
Integer => format!("INT"),
Serial => format!("INT IDENTITY(1,1)"),
Float => format!("FLOAT(24)"),
Double => format!("FLOAT(53)"),
UUID => format!("UNIQUEIDENTIFIER"),
......
......@@ -4,7 +4,10 @@
//! databases. They should be thoroughly tested via unit testing
use super::SqlGenerator;
use crate::types::{BaseType, Type};
use crate::{
functions::AutogenFunction,
types::{BaseType, Type, WrappedDefault},
};
/// A simple macro that will generate a schema prefix if it exists
macro_rules! prefix {
......@@ -54,8 +57,10 @@ impl SqlGenerator for MySql {
match bt {
Text => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)),
Varchar(_) => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)),
Char(_) => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)),
Primary => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)),
Integer => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)),
Serial => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)),
Float => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)),
Double => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)),
UUID => unimplemented!(),
......@@ -75,7 +80,18 @@ impl SqlGenerator for MySql {
false => "",
},
match (&tt.default).as_ref() {
Some(ref m) => format!(" DEFAULT '{}'", m),
Some(ref m) => match m {
WrappedDefault::Function(ref fun) => match fun {
AutogenFunction::CurrentTimestamp => format!(" DEFAULT CURRENT_TIMESTAMP")
},
WrappedDefault::Null => format!(" DEFAULT NULL"),
WrappedDefault::AnyText(ref val) => format!(" DEFAULT '{}'", val),
WrappedDefault::UUID(ref val) => format!(" DEFAULT '{}'", val),
WrappedDefault::Date(ref val) => format!(" DEFAULT '{:?}'", val),
WrappedDefault::Boolean(val) => format!(" DEFAULT {}", if *val { 1 } else { 0 }),
WrappedDefault::Custom(ref val) => format!(" DEFAULT '{}'", val),
_ => format!(" DEFAULT {}", m),
},
_ => format!(""),
},
match tt.nullable {
......@@ -119,6 +135,16 @@ impl SqlGenerator for MySql {
)
}
fn create_partial_index(
_table: &str,
_schema: Option<&str>,
_name: &str,
_type: &Type,
_conditions: &str,
) -> String {
panic!("Partial indices are not supported in MySQL")
}
fn drop_index(name: &str) -> String {
format!("DROP INDEX `{}`", name)
}
......@@ -136,13 +162,18 @@ impl SqlGenerator for MySql {
.collect();
format!(
"FOREIGN KEY({}) REFERENCES {}`{}`({})",
"FOREIGN KEY ({}) REFERENCES {}`{}`({})",
columns.join(","),
prefix!(schema),
table,
relation_columns.join(","),
)
}
fn add_primary_key(columns: &[String]) -> String {
let columns: Vec<_> = columns.into_iter().map(|c| format!("`{}`", c)).collect();
format!("PRIMARY KEY ({})", columns.join(","))
}
}
impl MySql {
......@@ -161,9 +192,11 @@ impl MySql {
0 => format!("VARCHAR"), // For "0" remove the limit
_ => format!("VARCHAR({})", l),
},
Char(l) => format!("CHAR({})", l),
/* "NOT NULL" is added here because normally primary keys are implicitly not-null */
Primary => format!("INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY"),
Integer => format!("INTEGER"),
Serial => format!("INTEGER AUTO_INCREMENT"),
Float => format!("FLOAT"),
Double => format!("DOUBLE"),
UUID => format!("CHAR(36)"),
......
......@@ -4,7 +4,10 @@
//! databases. They should be thoroughly tested via unit testing
use super::SqlGenerator;
use crate::types::{BaseType, Type};
use crate::{
functions::AutogenFunction,
types::{BaseType, Type, WrappedDefault},
};
/// A simple macro that will generate a schema prefix if it exists
macro_rules! prefix {
......@@ -56,8 +59,10 @@ impl SqlGenerator for Pg {
match bt {
Text => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)),
Varchar(_) => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)),
Char(_) => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)),
Primary => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)),
Integer => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)),
Serial => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)),
Float => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)),
Double => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)),
UUID => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)),
......@@ -77,7 +82,17 @@ impl SqlGenerator for Pg {
false => "",
},
match (&tt.default).as_ref() {
Some(ref m) => format!(" DEFAULT '{}'", m),
Some(ref m) => match m {
WrappedDefault::Function(ref fun) => match fun {
AutogenFunction::CurrentTimestamp => format!(" DEFAULT CURRENT_TIMESTAMP")
}
WrappedDefault::Null => format!(" DEFAULT NULL"),
WrappedDefault::AnyText(ref val) => format!(" DEFAULT '{}'", val),
WrappedDefault::UUID(ref val) => format!(" DEFAULT '{}'", val),
WrappedDefault::Date(ref val) => format!(" DEFAULT '{:?}'", val),
WrappedDefault::Custom(ref val) => format!(" DEFAULT '{}'", val),
_ => format!(" DEFAULT {}", m)
},
_ => format!(""),
},
match tt.nullable {
......@@ -146,6 +161,11 @@ impl SqlGenerator for Pg {
relation_columns.join(","),
)
}
fn add_primary_key(columns: &[String]) -> String {
let columns: Vec<_> = columns.into_iter().map(|c| format!("\"{}\"", c)).collect();
format!("PRIMARY KEY ({})", columns.join(","))
}
}
impl Pg {
......@@ -164,16 +184,18 @@ impl Pg {
0 => format!("VARCHAR"), // For "0" remove the limit
_ => format!("VARCHAR({})", l),
},
Char(l) => format!("CHAR({})", l),
/* "NOT NULL" is added here because normally primary keys are implicitly not-null */
Primary => format!("SERIAL PRIMARY KEY NOT NULL"),
Integer => format!("INTEGER"),
Serial => format!("SERIAL"),
Float => format!("FLOAT"),
Double => format!("DOUBLE PRECISION"),
UUID => format!("UUID"),
Boolean => format!("BOOLEAN"),
Date => format!("DATE"),
Time => format!("TIME"),
DateTime => format!("DATETIME"),
DateTime => format!("TIMESTAMPTZ"),
Json => format!("JSON"),
Binary => format!("BYTEA"),
Foreign(s, t, refs) => format!(
......
//! Sqlite3 implementation of a generator
use super::SqlGenerator;
use crate::types::{BaseType, Type};
use crate::{
functions::AutogenFunction,
types::{BaseType, Type, WrappedDefault},
};
/// A simple macro that will generate a schema prefix if it exists
macro_rules! prefix {
......@@ -52,8 +55,10 @@ impl SqlGenerator for Sqlite {
match bt {
Text => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)),
Varchar(_) => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)),
Char(_) => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)),
Primary => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)),
Integer => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)),
Serial => panic!("SQLite has no serials for non-primary key columns"),
Float => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)),
Double => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)),
UUID => panic!("`UUID` not supported by Sqlite3. Use `Text` instead!"),
......@@ -73,7 +78,18 @@ impl SqlGenerator for Sqlite {
false => "",
},
match (&tt.default).as_ref() {
Some(ref m) => format!(" DEFAULT '{}'", m),
Some(ref m) => match m {
WrappedDefault::Function(ref fun) => match fun {
AutogenFunction::CurrentTimestamp => format!(" DEFAULT CURRENT_TIMESTAMP")
}
WrappedDefault::Null => format!(" DEFAULT NULL"),
WrappedDefault::AnyText(ref val) => format!(" DEFAULT '{}'", val),
WrappedDefault::UUID(ref val) => format!(" DEFAULT '{}'", val),
WrappedDefault::Date(ref val) => format!(" DEFAULT '{:?}'", val),
WrappedDefault::Boolean(val) => format!(" DEFAULT {}", if *val { 1 } else { 0 }),
WrappedDefault::Custom(ref val) => format!(" DEFAULT '{}'", val),
_ => format!(" DEFAULT {}", m)
},
_ => format!(""),
},
match tt.nullable {
......@@ -90,7 +106,7 @@ impl SqlGenerator for Sqlite {
/// Create a multi-column index
fn create_index(table: &str, schema: Option<&str>, name: &str, _type: &Type) -> String {
format!(
"CREATE {} INDEX {}\"{}\" ON \"{}\" ({});",
"CREATE {} INDEX {}\"{}\" ON \"{}\" ({})",
match _type.unique {
true => "UNIQUE",
false => "",
......@@ -142,6 +158,11 @@ impl SqlGenerator for Sqlite {
relation_columns.join(","),
)
}
fn add_primary_key(columns: &[String]) -> String {
let columns: Vec<_> = columns.into_iter().map(|c| format!("\"{}\"", c)).collect();
format!("PRIMARY KEY ({})", columns.join(","))
}
}
impl Sqlite {
......@@ -160,7 +181,9 @@ impl Sqlite {
0 => format!("VARCHAR"), // For "0" remove the limit
_ => format!("VARCHAR({})", l),
},
Char(l) => format!("CHAR({})", l),
Primary => format!("INTEGER NOT NULL PRIMARY KEY"),
Serial => panic!("SQLite has no serials for non-primary key columns"),
Integer => format!("INTEGER"),
Float => format!("REAL"),
Double => format!("DOUBLE"),
......
/// Functions to generate default values
#[derive(Debug, Clone, PartialEq)]
pub enum AutogenFunction {
/// Gives the current timestamp
CurrentTimestamp,
}
/// Generates the current timestamp
pub fn current_timestamp() -> AutogenFunction {
AutogenFunction::CurrentTimestamp
}
......@@ -104,6 +104,7 @@ pub use integrations::*;
pub mod backend;
pub mod connectors;
pub mod functions;
pub mod migration;
pub mod table;
pub mod types;
......@@ -171,6 +172,13 @@ pub enum IndexChange {
columns: types::Type, // Should always be a `Index` type
},
AddPartialIndex {
index: String,
table: String,
columns: types::Type, // Should always be a `Index` type
conditions: String,
},
/// Remove a multi-column index
RemoveIndex(String, String),
}
......@@ -185,3 +193,10 @@ pub enum ForeignKeyChange {
relation_columns: Vec<String>,
},
}
/// An enum set that represents operations done to the primary key
#[derive(Clone)]
pub enum PrimaryKeyChange {
/// Adds a primary key to the table
AddPrimaryKey(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, foreign_keys) = t.make::<T>(false, schema);
let sql_changes = t.make::<T>(false, schema);
let name = t.meta.name().clone();
sql.push_str(&match change {
......@@ -71,8 +71,8 @@ impl Migration {
_ => unreachable!(),
});
sql.push_str(" (");
let l = cols.len();
for (i, slice) in cols.iter().enumerate() {
let l = sql_changes.columns.len();
for (i, slice) in sql_changes.columns.iter().enumerate() {
sql.push_str(slice);
if i < l - 1 {
......@@ -80,9 +80,14 @@ impl Migration {
}
}
let l = foreign_keys.len();
for (i, slice) in foreign_keys.iter().enumerate() {
if cols.len() > 0 && i == 0 {
if let Some(ref primary_key) = sql_changes.primary_key {
sql.push_str(", ");
sql.push_str(primary_key);
};
let l = sql_changes.foreign_keys.len();
for (i, slice) in sql_changes.foreign_keys.iter().enumerate() {
if sql_changes.columns.len() > 0 && i == 0 {
sql.push_str(", ")
}
......@@ -96,9 +101,9 @@ impl Migration {
sql.push_str(")");
// Add additional index columns
if indices.len() > 0 {
if sql_changes.indices.len() > 0 {
sql.push_str(";");
sql.push_str(&indices.join(";"));
sql.push_str(&sql_changes.indices.join(";"));
}
}
&mut DropTable(ref name) => sql.push_str(&T::drop_table(name, schema)),
......@@ -110,13 +115,13 @@ impl Migration {
}
&mut ChangeTable(ref mut t, ref mut cb) => {
cb(t);
let (cols, indices, fks) = t.make::<T>(true, schema);
let sql_changes = t.make::<T>(false, 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() {
let l = sql_changes.columns.len();
for (i, slice) in sql_changes.columns.iter().enumerate() {
sql.push_str(slice);
if i < l - 1 {
......@@ -124,9 +129,9 @@ impl Migration {
}
}
let l = fks.len();
for (i, slice) in fks.iter().enumerate() {
if cols.len() > 0 && i == 0 {
let l = sql_changes.foreign_keys.len();
for (i, slice) in sql_changes.foreign_keys.iter().enumerate() {
if sql_changes.columns.len() > 0 && i == 0 {
sql.push_str(", ")
}
......@@ -138,10 +143,16 @@ impl Migration {
}
}
if let Some(ref primary_key) = sql_changes.primary_key {
sql.push_str(", ");
sql.push_str("ADD ");
sql.push_str(primary_key);
};
// Add additional index columns
if indices.len() > 0 {
if sql_changes.indices.len() > 0 {
sql.push_str(";");
sql.push_str(&indices.join(";"));
sql.push_str(&sql_changes.indices.join(";"));
}
}
&mut CustomLine(ref line) => sql.push_str(line.as_str()),
......
......@@ -7,7 +7,7 @@
//! You can also change existing tables with a closure that can
//! then access individual columns in that table.
use super::{backend::SqlGenerator, ForeignKeyChange, IndexChange, TableChange};
use super::{backend::SqlGenerator, ForeignKeyChange, IndexChange, PrimaryKeyChange, TableChange};
use crate::types::Type;
use std::fmt::{Debug, Formatter, Result as FmtResult};
......@@ -29,12 +29,27 @@ impl Debug for ForeignKeyChange {
}
}
impl Debug for PrimaryKeyChange {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.write_str("PrimaryKeyChange")
}
}
#[derive(Debug, Clone)]
pub struct Table {
pub meta: TableMeta,
columns: Vec<TableChange>,
indices: Vec<IndexChange>,
foreign_keys: Vec<ForeignKeyChange>,
primary_key: Option<PrimaryKeyChange>,
}
#[derive(Debug, Clone)]
pub struct SqlChanges {
pub(crate) columns: Vec<String>,
pub(crate) indices: Vec<String>,
pub(crate) foreign_keys: Vec<String>,
pub(crate) primary_key: Option<String>,
}
impl Table {
......@@ -44,6 +59,7 @@ impl Table {
columns: vec![],
indices: vec![],
foreign_keys: vec![],
primary_key: None,
}
}
......@@ -102,6 +118,20 @@ impl Table {
});
}
pub fn add_partial_index<S: Into<String>>(&mut self, name: S, columns: Type, conditions: S) {
match columns.inner {
crate::types::BaseType::Index(_) => {}
_ => panic!("Calling `add_index` with a non-`Index` type is not allowed!"),
}
self.indices.push(IndexChange::AddPartialIndex {
table: self.meta.name.clone(),
index: name.into(),
columns,
conditions: conditions.into(),
});
}
/// Drop an index on this table
pub fn drop_index<S: Into<String>>(&mut self, name: S) {
self.indices.push(IndexChange::RemoveIndex(
......@@ -135,21 +165,18 @@ impl Table {
})
}
/// 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 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>) {
pub fn set_primary_key(&mut self, columns: &[&str]) {
let primary_key =
PrimaryKeyChange::AddPrimaryKey(columns.into_iter().map(|s| s.to_string()).collect());
self.primary_key = Some(primary_key);
}
/// Generate Sql for this table.
pub fn make<T: SqlGenerator>(&mut self, ex: bool, schema: Option<&str>) -> SqlChanges {
use ForeignKeyChange as KFC;
use IndexChange as IC;
use PrimaryKeyChange as PKC;
use TableChange as TC;
let columns = self
......@@ -164,7 +191,7 @@ impl Table {
})
.collect();
let indeces = self
let indices = self
.indices
.iter()
.map(|change| match change {
......@@ -173,10 +200,20 @@ impl Table {
table,
columns,
} => T::create_index(table, schema, index, columns),
IC::AddPartialIndex {
index,
table,
columns,
conditions,
} => T::create_partial_index(table, schema, index, columns, conditions),
IC::RemoveIndex(_, index) => T::drop_index(index),
})
.collect();
let primary_key = self.primary_key.as_ref().map(|pk| match pk {
PKC::AddPrimaryKey(ref cols) => T::add_primary_key(cols),
});
let foreign_keys = self
.foreign_keys
.iter()
......@@ -194,7 +231,12 @@ impl Table {
})
.collect();
(columns, indeces, foreign_keys)
SqlChanges {
columns,
indices,
foreign_keys,
primary_key,
}
}
}
......