diff options
author | Mx Kookie <kookie@spacekookie.de> | 2020-10-31 20:17:22 +0100 |
---|---|---|
committer | Mx Kookie <kookie@spacekookie.de> | 2020-12-21 05:19:21 +0100 |
commit | a9f4dd41c11bc4255823ebad7ceabf033cf32ecf (patch) | |
tree | d91064e96e60f444076c9ffc122a47afe3b22a72 /development/libs/barrel/src/backend | |
parent | 534807c7a3a3b96d219ec0ce9bb0b3029aa1bdd9 (diff) |
Add 'development/libs/barrel/' from commit 'f3b8ab47d3a3ad8d43dc2b89a5eec1c4e87b033d'
git-subtree-dir: development/libs/barrel
git-subtree-mainline: 518551bfa6ef7d6508b425afa4bfb3ddbd418141
git-subtree-split: f3b8ab47d3a3ad8d43dc2b89a5eec1c4e87b033d
Diffstat (limited to 'development/libs/barrel/src/backend')
-rw-r--r-- | development/libs/barrel/src/backend/mod.rs | 89 | ||||
-rw-r--r-- | development/libs/barrel/src/backend/mysql.rs | 162 | ||||
-rw-r--r-- | development/libs/barrel/src/backend/pg.rs | 164 | ||||
-rw-r--r-- | development/libs/barrel/src/backend/sqlite3.rs | 155 |
4 files changed, 570 insertions, 0 deletions
diff --git a/development/libs/barrel/src/backend/mod.rs b/development/libs/barrel/src/backend/mod.rs new file mode 100644 index 000000000000..107b718a7170 --- /dev/null +++ b/development/libs/barrel/src/backend/mod.rs @@ -0,0 +1,89 @@ +//! A backend module which provides a few generic traits +//! to implement SQL generation for different databases. +//! +//! It also re-exports the generators for existing databases +//! so they can be used more conveniently. + +#[cfg(feature = "mysql")] +mod mysql; +#[cfg(feature = "mysql")] +pub use self::mysql::MySql; + +#[cfg(feature = "pg")] +mod pg; +#[cfg(feature = "pg")] +pub use self::pg::Pg; + +#[cfg(feature = "sqlite3")] +mod sqlite3; +#[cfg(feature = "sqlite3")] +pub use self::sqlite3::Sqlite; + +#[allow(unused_imports)] +use crate::{types::Type, Migration}; + +/// An enum describing all supported Sql flavours +#[derive(Copy, Clone, Debug)] +pub enum SqlVariant { + #[cfg(feature = "sqlite3")] + Sqlite, + #[cfg(feature = "pg")] + Pg, + #[cfg(feature = "mysql")] + Mysql, + #[doc(hidden)] + __Empty, +} + +impl SqlVariant { + pub(crate) fn run_for(self, _migr: &Migration) -> String { + match self { + #[cfg(feature = "sqlite3")] + SqlVariant::Sqlite => _migr.make::<Sqlite>(), + + #[cfg(feature = "pg")] + SqlVariant::Pg => _migr.make::<Pg>(), + + #[cfg(feature = "mysql")] + SqlVariant::Mysql => _migr.make::<MySql>(), + + _ => panic!("You need to select an Sql variant!"), + } + } +} + +/// A generic SQL generator trait +pub trait SqlGenerator { + /// Create a new table with a name + fn create_table(name: &str, schema: Option<&str>) -> String; + + /// Create a new table with a name, only if it doesn't exist + fn create_table_if_not_exists(name: &str, schema: Option<&str>) -> String; + + /// Drop a table with a name + fn drop_table(name: &str, schema: Option<&str>) -> String; + + /// Drop a table with a name, only if it exists + fn drop_table_if_exists(name: &str, schema: Option<&str>) -> String; + + /// Rename a table from <old> to <new> + fn rename_table(old: &str, new: &str, schema: Option<&str>) -> String; + + /// Modify a table in some other way + fn alter_table(name: &str, schema: Option<&str>) -> String; + + /// Create a new column with a type + fn add_column(ex: bool, schema: Option<&str>, name: &str, _type: &Type) -> String; + + /// Drop an existing column from the table + fn drop_column(name: &str) -> String; + + /// Rename an existing column + fn rename_column(old: &str, new: &str) -> String; + + /// Create a multi-column index + fn create_index(table: &str, schema: Option<&str>, name: &str, _type: &Type) -> String; + + /// Drop a multi-column index + fn drop_index(name: &str) -> String; +} diff --git a/development/libs/barrel/src/backend/mysql.rs b/development/libs/barrel/src/backend/mysql.rs new file mode 100644 index 000000000000..5b27c69e334a --- /dev/null +++ b/development/libs/barrel/src/backend/mysql.rs @@ -0,0 +1,162 @@ +//! MySQL implementation of a generator +//! +//! This module generates strings that are specific to MySQL +//! databases. They should be thoroughly tested via unit testing + +use super::SqlGenerator; +use crate::types::{BaseType, Type}; + +/// A simple macro that will generate a schema prefix if it exists +macro_rules! prefix { + ($schema:expr) => { + $schema + .map(|s| format!("`{}`.", s)) + .unwrap_or_else(|| String::new()) + }; +} + +/// MySQL generator backend +pub struct MySql; +impl SqlGenerator for MySql { + fn create_table(name: &str, schema: Option<&str>) -> String { + format!("CREATE TABLE {}`{}`", prefix!(schema), name) + } + + fn create_table_if_not_exists(name: &str, schema: Option<&str>) -> String { + format!("CREATE TABLE {}`{}` IF NOT EXISTS", prefix!(schema), name) + } + + fn drop_table(name: &str, schema: Option<&str>) -> String { + format!("DROP TABLE {}`{}`", prefix!(schema), name) + } + + fn drop_table_if_exists(name: &str, schema: Option<&str>) -> String { + format!("DROP TABLE {}`{}` IF EXISTS", prefix!(schema), name) + } + + fn rename_table(old: &str, new: &str, schema: Option<&str>) -> String { + let schema = prefix!(schema); + format!("RENAME TABLE {}`{}` TO {}`{}`", schema, old, schema, new) + } + + fn alter_table(name: &str, schema: Option<&str>) -> String { + format!("ALTER TABLE {}`{}`", prefix!(schema), name) + } + + fn add_column(ex: bool, schema: Option<&str>, name: &str, tt: &Type) -> String { + let bt: BaseType = tt.get_inner(); + use self::BaseType::*; + let name = format!("`{}`", name); + + #[cfg_attr(rustfmt, rustfmt_skip)] /* This shouldn't be formatted. It's too long */ + format!( + "{}{}{}{}{}", + match bt { + Text => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)), + Varchar(_) => 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)), + Float => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)), + Double => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)), + UUID => unimplemented!(), + Json => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)), + Boolean => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)), + Date => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)), + Binary => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)), + Foreign(_, _, _) => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)), + Custom(_) => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)), + Array(it) => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(Array(Box::new(*it)), schema)), + Index(_) => unreachable!(), + }, + match tt.primary { + true => " PRIMARY KEY", + false => "", + }, + match (&tt.default).as_ref() { + Some(ref m) => format!(" DEFAULT '{}'", m), + _ => format!(""), + }, + match tt.nullable { + true => "", + false => " NOT NULL", + }, + match tt.unique { + true => " UNIQUE", + false => "", + }, + ) + } + + fn drop_column(name: &str) -> String { + format!("DROP COLUMN `{}`", name) + } + + fn rename_column(old: &str, new: &str) -> String { + format!("CHANGE COLUMN `{}` `{}`", old, new) + } + + fn create_index(table: &str, schema: Option<&str>, name: &str, _type: &Type) -> String { + // FIXME: Implement Mysql specific index builder here + format!( + "CREATE {} INDEX `{}` ON {}`{}` ({})", + match _type.unique { + true => "UNIQUE", + false => "", + }, + name, + prefix!(schema), + table, + match _type.inner { + BaseType::Index(ref cols) => cols + .iter() + .map(|col| format!("`{}`", col)) + .collect::<Vec<_>>() + .join(", "), + _ => unreachable!(), + } + ) + } + + fn drop_index(name: &str) -> String { + format!("DROP INDEX `{}`", name) + } +} + +impl MySql { + fn prefix(ex: bool) -> String { + match ex { + true => format!("ADD COLUMN "), + false => format!(""), + } + } + + fn print_type(t: BaseType, schema: Option<&str>) -> String { + use self::BaseType::*; + match t { + Text => format!("TEXT"), + Varchar(l) => match l { + 0 => format!("VARCHAR"), // For "0" remove the limit + _ => format!("VARCHAR({})", 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"), + Float => format!("FLOAT"), + Double => format!("DOUBLE"), + UUID => format!("CHAR(36)"), + Boolean => format!("BOOLEAN"), + Date => format!("DATE"), + Json => format!("JSON"), + Binary => format!("BYTEA"), + Foreign(s, t, refs) => format!( + "INTEGER REFERENCES {}{}({})", + prefix!(s), + t, + refs.0.join(",") + ), + Custom(t) => format!("{}", t), + Array(meh) => format!("{}[]", MySql::print_type(*meh, schema)), + Index(_) => unreachable!(), + } + } +} diff --git a/development/libs/barrel/src/backend/pg.rs b/development/libs/barrel/src/backend/pg.rs new file mode 100644 index 000000000000..1bea666efa1a --- /dev/null +++ b/development/libs/barrel/src/backend/pg.rs @@ -0,0 +1,164 @@ +//! Postgres implementation of a generator +//! +//! This module generates strings that are specific to Postgres +//! databases. They should be thoroughly tested via unit testing + +use super::SqlGenerator; +use crate::types::{BaseType, Type}; + +/// A simple macro that will generate a schema prefix if it exists +macro_rules! prefix { + ($schema:expr) => { + $schema + .map(|s| format!("\"{}\".", s)) + .unwrap_or_else(|| String::new()) + }; +} + +/// Postgres SQL generator backend +pub struct Pg; +impl SqlGenerator for Pg { + fn create_table(name: &str, schema: Option<&str>) -> String { + format!("CREATE TABLE {}\"{}\"", prefix!(schema), name) + } + + fn create_table_if_not_exists(name: &str, schema: Option<&str>) -> String { + format!("CREATE TABLE IF NOT EXISTS {}\"{}\"", prefix!(schema), name) + } + + fn drop_table(name: &str, schema: Option<&str>) -> String { + format!("DROP TABLE {}\"{}\"", prefix!(schema), name) + } + + fn drop_table_if_exists(name: &str, schema: Option<&str>) -> String { + format!("DROP TABLE IF EXISTS {}\"{}\"", prefix!(schema), name) + } + + fn rename_table(old: &str, new: &str, schema: Option<&str>) -> String { + let schema = prefix!(schema); + format!( + "ALTER TABLE {}\"{}\" RENAME TO {}\"{}\"", + schema, old, schema, new + ) + } + + fn alter_table(name: &str, schema: Option<&str>) -> String { + format!("ALTER TABLE {}\"{}\"", prefix!(schema), name) + } + + fn add_column(ex: bool, schema: Option<&str>, name: &str, tt: &Type) -> String { + let bt: BaseType = tt.get_inner(); + use self::BaseType::*; + + #[cfg_attr(rustfmt, rustfmt_skip)] /* This shouldn't be formatted. It's too long */ + format!( + "{}{}{}{}{}", + match bt { + Text => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)), + Varchar(_) => 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)), + 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)), + Json => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)), + Boolean => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)), + Date => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)), + Binary => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)), + Foreign(_, _, _) => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)), + Custom(_) => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)), + Array(it) => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(Array(Box::new(*it)), schema)), + Index(_) => unreachable!(), // Indices are handled via custom builder + }, + match tt.primary { + true => " PRIMARY KEY", + false => "", + }, + match (&tt.default).as_ref() { + Some(ref m) => format!(" DEFAULT '{}'", m), + _ => format!(""), + }, + match tt.nullable { + true => "", + false => " NOT NULL", + }, + match tt.unique { + true => " UNIQUE", + false => "", + }, + ) + } + + fn drop_column(name: &str) -> String { + format!("DROP COLUMN \"{}\"", name) + } + + fn rename_column(old: &str, new: &str) -> String { + format!("ALTER COLUMN \"{}\" RENAME TO \"{}\"", old, new) + } + + fn create_index(table: &str, schema: Option<&str>, name: &str, _type: &Type) -> String { + // FIXME: Implement PG specific index builder here + format!( + "CREATE {} INDEX \"{}\" ON {}\"{}\" ({})", + match _type.unique { + true => "UNIQUE", + false => "", + }, + name, + prefix!(schema), + table, + match _type.inner { + BaseType::Index(ref cols) => cols + .iter() + .map(|col| format!("\"{}\"", col)) + .collect::<Vec<_>>() + .join(", "), + _ => unreachable!(), + } + ) + } + + fn drop_index(name: &str) -> String { + format!("DROP INDEX \"{}\"", name) + } +} + +impl Pg { + fn prefix(ex: bool) -> String { + match ex { + true => format!("ADD COLUMN "), + false => format!(""), + } + } + + fn print_type(t: BaseType, schema: Option<&str>) -> String { + use self::BaseType::*; + match t { + Text => format!("TEXT"), + Varchar(l) => match l { + 0 => format!("VARCHAR"), // For "0" remove the limit + _ => format!("VARCHAR({})", l), + }, + /* "NOT NULL" is added here because normally primary keys are implicitly not-null */ + Primary => format!("SERIAL PRIMARY KEY NOT NULL"), + Integer => format!("INTEGER"), + Float => format!("FLOAT"), + Double => format!("DOUBLE PRECISION"), + UUID => format!("UUID"), + Boolean => format!("BOOLEAN"), + Date => format!("DATE"), + Json => format!("JSON"), + Binary => format!("BYTEA"), + Foreign(s, t, refs) => format!( + "INTEGER REFERENCES {}\"{}\"({})", + prefix!(s.or(schema.map(|s| s.into()))), + t, + refs.0.join(",") + ), + Custom(t) => format!("{}", t), + Array(meh) => format!("{}[]", Pg::print_type(*meh, schema)), + Index(_) => unreachable!(), // Indices are handled via custom builder + } + } +} diff --git a/development/libs/barrel/src/backend/sqlite3.rs b/development/libs/barrel/src/backend/sqlite3.rs new file mode 100644 index 000000000000..7c0d0438fa81 --- /dev/null +++ b/development/libs/barrel/src/backend/sqlite3.rs @@ -0,0 +1,155 @@ +//! Sqlite3 implementation of a generator + +use super::SqlGenerator; +use crate::types::{BaseType, Type}; + +/// A simple macro that will generate a schema prefix if it exists +macro_rules! prefix { + ($schema:expr) => { + $schema + .map(|s| format!("\"{}\".", s)) + .unwrap_or_else(|| String::new()) + }; +} + +/// We call this struct Sqlite instead of Sqlite3 because we hope not +/// to have to break the API further down the road +pub struct Sqlite; +impl SqlGenerator for Sqlite { + fn create_table(name: &str, schema: Option<&str>) -> String { + format!("CREATE TABLE {}\"{}\"", prefix!(schema), name) + } + + fn create_table_if_not_exists(name: &str, schema: Option<&str>) -> String { + format!("CREATE TABLE IF NOT EXISTS {}\"{}\"", prefix!(schema), name) + } + + fn drop_table(name: &str, schema: Option<&str>) -> String { + format!("DROP TABLE {}\"{}\"", prefix!(schema), name) + } + + fn drop_table_if_exists(name: &str, schema: Option<&str>) -> String { + format!("DROP TABLE IF EXISTS {}\"{}\"", prefix!(schema), name) + } + + fn rename_table(old: &str, new: &str, schema: Option<&str>) -> String { + let schema = prefix!(schema); + format!("ALTER TABLE {}\"{}\" RENAME TO \"{}\"", schema, old, new) + } + + fn alter_table(name: &str, schema: Option<&str>) -> String { + format!("ALTER TABLE {}\"{}\"", prefix!(schema), name) + } + + fn add_column(ex: bool, _: Option<&str>, name: &str, tt: &Type) -> String { + let bt: BaseType = tt.get_inner(); + use self::BaseType::*; + + #[cfg_attr(rustfmt, rustfmt_skip)] /* This shouldn't be formatted. It's too long */ + format!( + // SQL base - default - nullable - unique + "{}{}{}{}{}", + match bt { + Text => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)), + Varchar(_) => 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)), + 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!"), + Json => panic!("`Json` not supported by Sqlite3. Use `Text` instead!"), + Boolean => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)), + Date => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)), + Binary => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)), + Foreign(_, _, _) => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)), + Custom(_) => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)), + Array(it) => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(Array(Box::new(*it)))), + Index(_) => unreachable!(), // Indices are handled via custom builders + }, + match tt.primary { + true => " PRIMARY KEY", + false => "", + }, + match (&tt.default).as_ref() { + Some(ref m) => format!(" DEFAULT '{}'", m), + _ => format!(""), + }, + match tt.nullable { + true => "", + false => " NOT NULL", + }, + match tt.unique { + true => " UNIQUE", + false => "", + } + ) + } + + /// Create a multi-column index + fn create_index(table: &str, schema: Option<&str>, name: &str, _type: &Type) -> String { + format!( + "CREATE {} INDEX {}\"{}\" ON \"{}\" ({});", + match _type.unique { + true => "UNIQUE", + false => "", + }, + prefix!(schema), + name, + table, + match _type.inner { + BaseType::Index(ref cols) => cols + .iter() + .map(|col| format!("\"{}\"", col)) + .collect::<Vec<_>>() + .join(", "), + _ => unreachable!(), + } + ) + } + + /// Drop a multi-column index + fn drop_index(name: &str) -> String { + format!("DROP INDEX \"{}\"", name) + } + + fn drop_column(_: &str) -> String { + panic!("Sqlite does not support dropping columns!") + } + + fn rename_column(_: &str, _: &str) -> String { + panic!("Sqlite does not support renaming columns!") + } +} + +impl Sqlite { + fn prefix(ex: bool) -> String { + match ex { + true => format!("ADD COLUMN "), + false => format!(""), + } + } + + fn print_type(t: BaseType) -> String { + use self::BaseType::*; + match t { + Text => format!("TEXT"), + Varchar(l) => match l { + 0 => format!("VARCHAR"), // For "0" remove the limit + _ => format!("VARCHAR({})", l), + }, + Primary => format!("INTEGER NOT NULL PRIMARY KEY"), + Integer => format!("INTEGER"), + Float => format!("REAL"), + Double => format!("DOUBLE"), + UUID => unimplemented!(), + Boolean => format!("BOOLEAN"), + Date => format!("DATE"), + Json => panic!("Json is not supported by Sqlite3"), + Binary => format!("BINARY"), + Foreign(_, t, refs) => format!("INTEGER REFERENCES {}({})", t, refs.0.join(",")), + Custom(t) => format!("{}", t), + Array(meh) => format!("{}[]", Sqlite::print_type(*meh)), + Index(_) => unimplemented!(), + } + } +} |