barrel-0.7.0/.cargo_vcs_info.json0000644000000001120000000000000123000ustar { "git": { "sha1": "060ed8ac69d88e84186978ffd6898a93d7fba041" } } barrel-0.7.0/.envrc000064400000000000000000000000270000000000000121710ustar 00000000000000eval "$(lorri direnv)" barrel-0.7.0/.gitignore000064400000000000000000000000400000000000000130360ustar 00000000000000 /target/ **/*.rs.bk Cargo.lock barrel-0.7.0/.travis.yml000064400000000000000000000023650000000000000131730ustar 00000000000000# Some basic stuff about what we're doing here dist: trusty language: rust services: docker sudo: required cache: cargo rust: - stable # Only build on nightly rust for now - nightly # This is required for coveralls addons: apt: packages: - libcurl4-openssl-dev - libelf-dev - libdw-dev - binutils-dev - cmake sources: - kalakris-cmake # If nightly explodes we don't care aaas much matrix: allow_failures: # fail on nightly while we don't have a stable build # - rust: nightly # This is a pretty big hack and only really needed on the first of a build chain before_script: - cargo install cargo-travis -f && export PATH=$HOME/.cargo/bin:$PATH # Build, test, benchmark, document. Gogogogo! script: - cargo build --verbose --all --features "pg mysql sqlite3 unstable" - cargo test --verbose --all --features "pg mysql sqlite3 unstable" - cargo test --verbose --all --features "diesel pg unstable" # Diesel module _demands_ only one backend - cargo doc --features "pg mysql sqlite3 unstable" # Upload the whole mess after_success: - cargo coveralls --verbose --features "pg mysql sqlite3 unstable" # AND GOD DAMN IT LET ME SLEEP! notifications: email: on_success: never on_failure: never barrel-0.7.0/Cargo.lock0000644000000124140000000000000102630ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "barrel" version = "0.7.0" dependencies = [ "diesel", "tempfile", ] [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "byteorder" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "diesel" version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2de9deab977a153492a1468d1b1c0662c1cf39e5ea87d0c060ecd59ef18d8c" dependencies = [ "byteorder", "diesel_derives", ] [[package]] name = "diesel_derives" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "getrandom" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "libc" version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7eb0c4e9c72ee9d69b767adebc5f4788462a3b45624acd919475c92597bcaf4f" [[package]] name = "ppv-lite86" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" [[package]] name = "proc-macro2" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" dependencies = [ "unicode-xid", ] [[package]] name = "quote" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" dependencies = [ "libc", "rand_chacha", "rand_core", "rand_hc", ] [[package]] name = "rand_chacha" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c026d7df8b298d90ccbbc5190bd04d85e159eaf5576caeacf8741da93ccbd2e5" dependencies = [ "getrandom", ] [[package]] name = "rand_hc" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" dependencies = [ "rand_core", ] [[package]] name = "redox_syscall" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05ec8ca9416c5ea37062b502703cd7fcb207736bc294f6e0cf367ac6fc234570" dependencies = [ "bitflags", ] [[package]] name = "remove_dir_all" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ "winapi", ] [[package]] name = "syn" version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] [[package]] name = "tempfile" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ "cfg-if", "libc", "rand", "redox_syscall", "remove_dir_all", "winapi", ] [[package]] name = "unicode-xid" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" barrel-0.7.0/Cargo.toml0000644000000027600000000000000103110ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "barrel" version = "0.7.0" authors = ["Katharina Fey ", "Rob Rowe "] description = "A powerful schema migration building API for Rust" homepage = "https://rust-db.github.io/barrel" documentation = "https://docs.rs/barrel" readme = "README.md" keywords = ["sql", "database", "schema", "migration"] categories = ["database", "development-tools"] license = "MIT/X11 OR Apache-2.0" repository = "https://github.com/rust-db/barrel" [package.metadata.docs.rs] features = ["mysql", "sqlite3", "pg", "unstable"] [[example]] name = "pg_strings" required-features = ["pg"] [[example]] name = "sqlite_strings" required-features = ["sqlite3"] [dependencies.diesel_rs] version = ">= 1.2, < 2.0" optional = true default_features = false package = "diesel" [dependencies.tempfile] version = "3" optional = true [features] default = [] diesel = ["tempfile", "diesel_rs"] mssql = [] mysql = [] pg = [] sqlite3 = [] unstable = [] barrel-0.7.0/Cargo.toml.orig0000644000000022520000000000000112440ustar [package] name = "barrel" version = "0.7.0" description = "A powerful schema migration building API for Rust" authors = ["Katharina Fey ", "Rob Rowe "] license = "MIT/X11 OR Apache-2.0" edition = "2018" readme = "README.md" repository = "https://github.com/rust-db/barrel" homepage = "https://rust-db.github.io/barrel" documentation = "https://docs.rs/barrel" categories = [ "database", "development-tools"] keywords = ["sql", "database", "schema", "migration"] [package.metadata.docs.rs] # We can't build documentation with the `diesel` flag enabled # because then the project no longer builds. features = ["mysql", "sqlite3", "pg", "unstable"] [[example]] name = "pg_strings" required-features = ["pg"] [[example]] name = "sqlite_strings" required-features = ["sqlite3"] [features] default = [] diesel = ["tempfile", "diesel_rs"] sqlite3 = [] mysql = [] mssql = [] pg = [] # Enables unstable (in-development) features, # even for stable version upgrades unstable = [] [dependencies] tempfile = { version = "3", optional = true } diesel_rs = { version = ">= 1.2, < 2.0", package = "diesel", default_features = false, optional = true } barrel-0.7.0/Cargo.toml.orig000064400000000000000000000022520000000000000137440ustar 00000000000000[package] name = "barrel" version = "0.7.0" description = "A powerful schema migration building API for Rust" authors = ["Katharina Fey ", "Rob Rowe "] license = "MIT/X11 OR Apache-2.0" edition = "2018" readme = "README.md" repository = "https://github.com/rust-db/barrel" homepage = "https://rust-db.github.io/barrel" documentation = "https://docs.rs/barrel" categories = [ "database", "development-tools"] keywords = ["sql", "database", "schema", "migration"] [package.metadata.docs.rs] # We can't build documentation with the `diesel` flag enabled # because then the project no longer builds. features = ["mysql", "sqlite3", "pg", "unstable"] [[example]] name = "pg_strings" required-features = ["pg"] [[example]] name = "sqlite_strings" required-features = ["sqlite3"] [features] default = [] diesel = ["tempfile", "diesel_rs"] sqlite3 = [] mysql = [] mssql = [] pg = [] # Enables unstable (in-development) features, # even for stable version upgrades unstable = [] [dependencies] tempfile = { version = "3", optional = true } diesel_rs = { version = ">= 1.2, < 2.0", package = "diesel", default_features = false, optional = true } barrel-0.7.0/FUNDING.yml000064400000000000000000000000240000000000000126650ustar 00000000000000github: spacekookie barrel-0.7.0/LICENSE000064400000000000000000000020560000000000000120640ustar 00000000000000MIT License Copyright (c) 2018 Katharina Fey Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. barrel-0.7.0/README.md000064400000000000000000000051340000000000000123360ustar 00000000000000![](assets/logo.svg) [![](https://travis-ci.org/rust-db/barrel.svg?branch=master)](https://travis-ci.org/rust-db/barrel) [![](https://docs.rs/barrel/badge.svg)](https://docs.rs/barrel/) [![](https://img.shields.io/crates/v/barrel.svg)](https://crates.io/crates/barrel) [![](https://img.shields.io/crates/d/barrel.svg)](https://crates.io/crates/barrel) A powerful database schema builder, that lets you write your SQL migrations in Rust! `barrel` offers callback-style builder functions for SQL migrations and is designed to be flexible, portable and fun to use. It provides you with a common interface over SQL, with additional database-specific builders. This way you can focus on your Rust code, without having to worry about SQL. ## Example The following example will help you get started ```rust use barrel::{types, Migration}; use barrel::backend::Pg; fn main() { let mut m = Migration::new(); m.create_table("users", |t| { t.add_column("name", types::varchar(255)); t.add_column("age", types::integer()); t.add_column("owns_plushy_sharks", types::boolean()); }); println!("{}", m.make::()); } ``` ## Using Diesel Since `diesel 1.2.0` it's possible to now use `barrel` for migrations with `diesel`. A guide with some more information on how to get started can be found [here](https://github.com/spacekookie/barrel/blob/master/guides/diesel-setup.md) ### Migration guide If you've been using `barrel` to write migrations for `diesel` before the `0.5.0` release, some migration of your migrations will be required. Since `0.5.0` the way types are constructed changed. Instead of constructing a type with `Types::VarChar(255)` (an enum variant), the types are now provided by a module called `types` and builder functions. The same type would now be `types::varchar(255)` (a function call), which then returns a `Type` enum. You can also directly created your own `Type` builders this way. Check the docs for details! ## License `barrel` is free software: you can redistribute it and/or modify it under the terms of the MIT Public License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MIT Public License for more details. ## Conduct In the interest of fostering an open and welcoming environment, the `barrel` project pledges to making participation a harassment-free experience for everyone. See [Code of Conduct](code_of_conduct.md) for details. In case of violations, e-mail [kookie@spacekookie.de](mailto:kookie@spacekookie.de). barrel-0.7.0/appveyor.yml000064400000000000000000000006110000000000000134420ustar 00000000000000build: false build_script: - cargo test --verbose %cargoflags% --all-features environment: matrix: - channel: stable target: x86_64-pc-windows-gnu install: - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe - rustup-init -yv --default-toolchain %channel% --default-host %target% - set PATH=%PATH%;%USERPROFILE%\.cargo\bin - rustc -vV - cargo -vV barrel-0.7.0/assets/logo.svg000064400000000000000000000312120000000000000140360ustar 00000000000000 image/svg+xml SQL barrel-0.7.0/code_of_conduct.md000064400000000000000000000062330000000000000145170ustar 00000000000000# Contributor Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at kookie@spacekookie.de All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [https://contributor-covenant.org/version/1/4][version] [homepage]: https://contributor-covenant.org [version]: https://contributor-covenant.org/version/1/4/ barrel-0.7.0/guides/diesel-setup.md000064400000000000000000000030600000000000000152600ustar 00000000000000# Diesel setup ### Disclaimer > The barrel crate is still in an early state of development and should not be considered "stable". > > Old migrations might break because the API was changed. > Features might be removed or replaced! **Before 1.0 this crate is not stable > and should __not__ be used in production** > – just wanted to make that loud and clear :) --- Using rust migrations (via `barrel`) with `diesel` is really simple. First make sure that you installed the `diesel_cli` with the `barrel-migrations` feature flag: ```bash ~ cargo install diesel_cli --features="barrel-migrations,barrel/sqlite3,sqlite" ``` **Important:** you can only select one (1) backend with diesel. Whichever you select will determine the migration files that are generated later. ```toml [dependencies] diesel = { version = "1.4", features = ["sqlite"] } # ... ``` From this point using `diesel` is very similar to how you normally use it. The only difference is that you should provide a `--format` flag when letting diesel generate a migration for you. Running migrations doesn't change. ```bash ~ diesel migration generate --format="barrel" ~ diesel migration run ``` A migration file generated by diesel will look as follows ```rust /// Handle up migrations fn up(migr: &mut Migration) {} /// Handle down migrations fn down(migr: &mut Migration) {} ``` The object provided as a function parameter is a mutable `Migration` object which you can operate on. Please refer to [the docs](https://docs.rs/barrel/0.2.0/barrel/migration/struct.Migration.html) for API specifics. barrel-0.7.0/rustfmt.toml000064400000000000000000000000210000000000000134460ustar 00000000000000edition = "2018" barrel-0.7.0/shell.nix000064400000000000000000000002070000000000000127020ustar 00000000000000with import {}; stdenv.mkDerivation { name = "qaul"; buildInputs = with pkgs; [ rustracer rustup clangStdenv ]; } barrel-0.7.0/src/TODO000064400000000000000000000030010000000000000123250ustar 00000000000000The following file outlines some of the changes that should be made to barrel, before we can promote a version to 1.0. This is in no way an exhausive list, but certainly a good starting point for contributors to work with. There are several sections of issues, some of which were already reported on the repo, others have not. == Bugs - https://github.com/rust-db/barrel/issues/79 - https://github.com/rust-db/barrel/issues/80 - https://github.com/rust-db/barrel/issues/81 - https://github.com/rust-db/barrel/issues/82 - https://github.com/rust-db/barrel/issues/83 == Features - Tests should run against a database backend, not just a static string. This way we can also enforce compatibility with a specific database version. - - *Testing*: The tests should run against an actual database to make sure that the syntax is valid. We still run into syntax issues from time to time. As also mentioned here: https://github.com/rust-db/barrel/issues/8#issue-297373013 - *Integrate Fork*: One of our freelancers was working with barrel for some time and eventually had to create a fork in order to do fixes. Currently we are using his fork but I would really like to switch back to mainline barrel asap. - Merge his PRs if possible: https://github.com/rust-db/barrel/pulls/aknuds1 - There are probably other fixes he has not opened a PR for. Those should be ported as well. Maybe you can scan his history to see what is needed. https://github.com/aknuds1/barrel/commits/master barrel-0.7.0/src/backend/mod.rs000064400000000000000000000065730000000000000143720ustar 00000000000000//! 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; #[cfg(feature = "mssql")] mod mssql; #[cfg(feature = "mssql")] pub use self::mssql::MsSql; #[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, #[cfg(feature = "mssql")] Mssql, #[doc(hidden)] __Empty, } impl SqlVariant { pub(crate) fn run_for(self, _migr: &Migration) -> String { match self { #[cfg(feature = "sqlite3")] SqlVariant::Sqlite => _migr.make::(), #[cfg(feature = "pg")] SqlVariant::Pg => _migr.make::(), #[cfg(feature = "mysql")] SqlVariant::Mysql => _migr.make::(), #[cfg(feature = "mssql")] SqlVariant::Mssql => _migr.make::(), _ => 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 to 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; /// Create a constraint fn create_constraint(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; /// Add a foreign key fn add_foreign_key( columns: &[String], table: &str, relation_columns: &[String], schema: Option<&str>, ) -> String; fn add_primary_key(columns: &[String]) -> String; } barrel-0.7.0/src/backend/mssql.rs000064400000000000000000000212140000000000000147370ustar 00000000000000//! Microsoft SQL Server implementation of a generator //! //! This module generates strings that are specific to SQL Server //! databases. They should be thoroughly tested via unit testing use super::SqlGenerator; 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 { ($schema:expr) => { $schema .map(|s| format!("[{}].", s)) .unwrap_or_else(|| String::new()) }; } /// 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()) }; } /// 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) } fn create_table_if_not_exists(name: &str, schema: Option<&str>) -> String { let table = format!("{}[{}]", quoted_prefix!(schema), name); match schema { None => { format!( "IF NOT EXISTS (SELECT * FROM sys.tables WHERE name='{table}') CREATE TABLE {quoted}", table = name, quoted = table, ) } Some(schema) => { format!( "IF NOT EXISTS (SELECT * FROM sys.tables WHERE name='{table}' AND SCHEMA_NAME(schema_id) = '{schema}') CREATE TABLE {quoted}", table = name, quoted = table, schema = schema, ) } } } fn drop_table(name: &str, schema: Option<&str>) -> String { format!("DROP TABLE {}[{}]", quoted_prefix!(schema), name) } fn drop_table_if_exists(name: &str, schema: Option<&str>) -> String { format!("DROP TABLE IF EXISTS {}[{}]", quoted_prefix!(schema), name) } fn rename_table(old: &str, new: &str, schema: Option<&str>) -> String { let prefix = prefix!(schema); format!(r#"EXEC sp_rename '{}{}', '{}'"#, prefix, old, new) } fn alter_table(name: &str, schema: Option<&str>) -> String { format!("ALTER TABLE {}[{}]", quoted_prefix!(schema), name) } fn add_column(ex: bool, schema: Option<&str>, name: &str, tt: &Type) -> String { let bt: BaseType = tt.get_inner(); let name_and_type = match bt { BaseType::Array(_) => panic!("Arrays are not supported with SQL Server"), BaseType::Index(_) => unreachable!("Indices are handled via custom builder"), _ => format!( "{}[{}] {}", MsSql::prefix(ex), name, MsSql::print_type(bt, schema) ), }; let primary_key_indicator = match tt.primary { true => " PRIMARY KEY", false => "", }; let default_indicator = match (&tt.default).as_ref() { Some(ref m) => Self::default(m), _ => format!(""), }; let nullable_indicator = match tt.nullable { true => "", false => " NOT NULL", }; let unique_indicator = match tt.unique { true => " UNIQUE", false => "", }; format!( "{}{}{}{}{}", // `ADD name VARCHAR(max)` or `name VARCHAR(max)` name_and_type, // `PRIMARY KEY` or nothing primary_key_indicator, // `DEFAULT 'foo'` or nothing default_indicator, // `NOT NULL` or nothing nullable_indicator, // `UNIQUE or nothing` unique_indicator ) } fn drop_column(name: &str) -> String { format!("DROP COLUMN [{}]", name) } fn rename_column(old: &str, new: &str) -> String { format!(r#"EXEC sp_rename '{}', '{}'"#, 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::>() .join(", "), _ => unreachable!(), } ) } fn create_constraint(name: &str, _type: &Type) -> String { let (r#type, columns) = match _type.inner { BaseType::Constraint(ref r#type, ref columns) => ( r#type.clone(), columns .iter() .map(|col| format!("[{}]", col)) .collect::>(), ), _ => unreachable!(), }; format!("CONSTRAINT [{}] {} ({})", name, r#type, columns.join(", "),) } 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(","), ) } 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 { fn prefix(ex: bool) -> String { match ex { true => format!("ADD "), 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(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"), Boolean => format!("BIT"), Date => format!("DATE"), Time => format!("TIME"), DateTime => format!("DATETIME2"), Json => format!("JSON"), Binary => format!("VARBINARY(MAX)"), Foreign(s, t, refs) => format!( "INT REFERENCES {}[{}]({})", quoted_prefix!(s.or(schema.map(|s| s.into()))), t, refs.0 .iter() .map(|r| format!("[{}]", r)) .collect::>() .join(",") ), Custom(t) => format!("{}", t), Array(meh) => format!("{}[]", MsSql::print_type(*meh, schema)), Index(_) => unreachable!("Indices are handled via custom builder"), Constraint(_, _) => unreachable!("Constraints are handled via custom builder"), } } } barrel-0.7.0/src/backend/mysql.rs000064400000000000000000000241770000000000000147600ustar 00000000000000//! 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::{ functions::AutogenFunction, types::{BaseType, ReferentialAction, Type, WrappedDefault}, }; /// 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(); let btc = bt.clone(); use self::BaseType::*; let name = format!("`{}`", name); let nullable_definition = match tt.nullable { true => "", false => " NOT NULL", }; let unique_definition = match tt.unique { true => " UNIQUE", false => "", }; let primary_definition = match tt.primary { true => " PRIMARY KEY", false => "", }; #[cfg_attr(rustfmt, rustfmt_skip)] /* This shouldn't be formatted. It's too long */ let base_type_definition = match bt { Text => format!("{}{} {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Varchar(_) => format!("{}{} {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Char(_) => format!("{}{} {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Primary => format!("{}{} {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Integer => format!("{}{} {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Serial => format!("{}{} {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Float => format!("{}{} {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Double => format!("{}{} {}", Self::prefix(ex), name, Self::print_type(bt, schema)), UUID => unimplemented!(), Json => format!("{}{} {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Boolean => format!("{}{} {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Date => format!("{}{} {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Time => format!("{}{} {}", Self::prefix(ex), name, Self::print_type(bt, schema)), DateTime => format!("{}{} {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Binary => format!("{}{} {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Foreign(_, _, _, _, _) => format!("{}{} INTEGER{}, FOREIGN KEY ({}) {}", Self::prefix(ex), name, nullable_definition, name, Self::print_type(bt, schema)), Custom(_) => format!("{}{} {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Array(it) => format!("{}{} {}", Self::prefix(ex), name, Self::print_type(Array(Box::new(*it)), schema)), Index(_) => unreachable!("Indices are handled via custom builder"), Constraint(_, _) => unreachable!("Constraints are handled via custom builder"), }; let default_definition = match (&tt.default).as_ref() { 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 btc { Foreign(_, _, _, _, _) => { format!( "{}{}{}{}", base_type_definition, primary_definition, default_definition, unique_definition, ) } _ => { format!( "{}{}{}{}{}", base_type_definition, primary_definition, default_definition, nullable_definition, unique_definition, ) } } } 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::>() .join(", "), _ => unreachable!(), } ) } fn create_constraint(name: &str, _type: &Type) -> String { let (r#type, columns) = match _type.inner { BaseType::Constraint(ref r#type, ref columns) => ( r#type.clone(), columns .iter() .map(|col| format!("`{}`", col)) .collect::>(), ), _ => unreachable!(), }; format!("CONSTRAINT `{}` {} ({})", name, r#type, columns.join(", "),) } 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) } 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(","), ) } 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 { 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), }, 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)"), Boolean => format!("BOOLEAN"), Date => format!("DATE"), Time => format!("TIME"), DateTime => format!("DATETIME"), Json => format!("JSON"), Binary => format!("BYTEA"), Foreign(s, t, refs, on_update, on_delete) => { let d = match on_delete { ReferentialAction::Unset => String::from(""), _ => format!(" {}", on_delete.on_delete()), }; let u = match on_update { ReferentialAction::Unset => String::from(""), _ => format!(" {}", on_update.on_update()), }; format!( "REFERENCES {}{}({}){}{}", prefix!(s), t, refs.0.join(","), u, d ) } Custom(t) => format!("{}", t), Array(meh) => format!("{}[]", MySql::print_type(*meh, schema)), Index(_) => unreachable!(), Constraint(_, _) => unreachable!(), } } } barrel-0.7.0/src/backend/pg.rs000064400000000000000000000240340000000000000142110ustar 00000000000000//! 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::{ functions::AutogenFunction, types::{BaseType, ReferentialAction, Type, WrappedDefault}, }; /// 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(); let btc = bt.clone(); use self::BaseType::*; let primary_definition = match tt.primary { true => " PRIMARY KEY", false => "", }; let default_definition = match (&tt.default).as_ref() { 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!(""), }; let nullable_definition = match tt.nullable { true => "", false => " NOT NULL", }; let unique_definition = match tt.unique { true => " UNIQUE", false => "", }; #[cfg_attr(rustfmt, rustfmt_skip)] /* This shouldn't be formatted. It's too long */ let base_type_definition = match bt { Text => format!("{}\"{}\" {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Varchar(_) => format!("{}\"{}\" {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Char(_) => format!("{}\"{}\" {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Primary => format!("{}\"{}\" {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Integer => format!("{}\"{}\" {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Serial => format!("{}\"{}\" {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Float => format!("{}\"{}\" {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Double => format!("{}\"{}\" {}", Self::prefix(ex), name, Self::print_type(bt, schema)), UUID => format!("{}\"{}\" {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Json => format!("{}\"{}\" {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Boolean => format!("{}\"{}\" {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Date => format!("{}\"{}\" {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Time => format!("{}\"{}\" {}", Self::prefix(ex), name, Self::print_type(bt, schema)), DateTime => format!("{}\"{}\" {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Binary => format!("{}\"{}\" {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Foreign(_, _, _, _, _) => format!("{}\"{}\" INTEGER{}, FOREIGN KEY ({}) {}", Self::prefix(ex), name, nullable_definition, name, Self::print_type(bt, schema)), Custom(_) => format!("{}\"{}\" {}", Self::prefix(ex), name, Self::print_type(bt, schema)), Array(it) => format!("{}\"{}\" {}", Self::prefix(ex), name, Self::print_type(Array(Box::new(*it)), schema)), Index(_) => unreachable!("Indices are handled via custom builder"), Constraint(_, _) => unreachable!("Constraints are handled via custom builder"), }; match btc { Foreign(_, _, _, _, _) => { format!( "{}{}{}{}", base_type_definition, primary_definition, default_definition, unique_definition, ) } _ => { format!( "{}{}{}{}{}", base_type_definition, primary_definition, default_definition, nullable_definition, unique_definition, ) } } } fn drop_column(name: &str) -> String { format!("DROP COLUMN \"{}\"", name) } fn rename_column(old: &str, new: &str) -> String { format!("RENAME COLUMN \"{}\" 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::>() .join(", "), _ => unreachable!(), } ) } fn create_constraint(name: &str, _type: &Type) -> String { let (r#type, columns) = match _type.inner { BaseType::Constraint(ref r#type, ref columns) => ( r#type.clone(), columns .iter() .map(|col| format!("\"{}\"", col)) .collect::>(), ), _ => unreachable!(), }; format!( "CONSTRAINT \"{}\" {} ({})", name, r#type, columns.join(", "), ) } 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(","), ) } 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 { 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), }, 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!("TIMESTAMP"), Json => format!("JSON"), Binary => format!("BYTEA"), Foreign(s, t, refs, on_update, on_delete) => { let d = match on_delete { ReferentialAction::Unset => String::from(""), _ => format!(" {}", on_delete.on_delete()), }; let u = match on_update { ReferentialAction::Unset => String::from(""), _ => format!(" {}", on_update.on_update()), }; format!( "REFERENCES {}\"{}\"({}){}{}", prefix!(s.or(schema.map(|s| s.into()))), t, refs.0.join(","), u, d ) } Custom(t) => format!("{}", t), Array(meh) => format!("{}[]", Pg::print_type(*meh, schema)), Index(_) => unimplemented!("Indices are handled via custom builder"), Constraint(_, _) => unreachable!("Constraints are handled via custom builder"), } } } barrel-0.7.0/src/backend/sqlite3.rs000064400000000000000000000237640000000000000152000ustar 00000000000000//! Sqlite3 implementation of a generator use super::SqlGenerator; use crate::{ functions::AutogenFunction, types::{BaseType, ReferentialAction, Type, WrappedDefault}, }; /// 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(); let btc = bt.clone(); use self::BaseType::*; let primary_definition = match tt.primary { true => " PRIMARY KEY", false => "", }; let default_definition = match (&tt.default).as_ref() { 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!(""), }; let nullable_definition = match tt.nullable { true => "", false => " NOT NULL", }; let unique_definition = match tt.unique { true => " UNIQUE", false => "", }; #[cfg_attr(rustfmt, rustfmt_skip)] /* This shouldn't be formatted. It's too long */ let base_type_definition = 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!"), 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)), Time => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)), DateTime => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)), Binary => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)), Foreign(_, _, _, _, _) => format!("{}\"{}\" INTEGER{} REFERENCES {}", Sqlite::prefix(ex), name, nullable_definition, 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 builder"), Constraint(_, _) => unreachable!("Constraints are handled via custom builder"), }; match btc { Foreign(_, _, _, _, _) => { format!( "{}{}{}{}", base_type_definition, primary_definition, default_definition, unique_definition, ) } _ => { format!( "{}{}{}{}{}", base_type_definition, primary_definition, default_definition, nullable_definition, unique_definition, ) } } //#[cfg_attr(rustfmt, rustfmt_skip)] /* This shouldn't be formatted. It's too long */ //format!( // // SQL base - default - nullable - unique // "{}{}{}{}{}", // base_type_definition, // primary_definition, // default_definition, // nullable_definition, // unique_definition //) } /// 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::>() .join(", "), _ => unreachable!(), } ) } fn create_constraint(name: &str, _type: &Type) -> String { let (r#type, columns) = match _type.inner { BaseType::Constraint(ref r#type, ref columns) => ( r#type.clone(), columns .iter() .map(|col| format!("\"{}\"", col)) .collect::>(), ), _ => unreachable!(), }; format!( "CONSTRAINT \"{}\" {} ({})", name, r#type, columns.join(", "), ) } /// 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!") } 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(","), ) } 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 { 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), }, 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"), UUID => unimplemented!(), Boolean => format!("BOOLEAN"), Date => format!("DATE"), Time => format!("TIME"), DateTime => format!("DATETIME"), Json => panic!("Json is not supported by Sqlite3"), Binary => format!("BINARY"), Foreign(_, t, refs, on_update, on_delete) => { let d = match on_delete { ReferentialAction::Unset => String::from(""), _ => format!(" {}", on_delete.on_delete()), }; let u = match on_update { ReferentialAction::Unset => String::from(""), _ => format!(" {}", on_update.on_update()), }; format!("{}({}){}{}", t, refs.0.join(","), u, d) } Custom(t) => format!("{}", t), Array(meh) => format!("{}[]", Sqlite::print_type(*meh)), Index(_) => unimplemented!(), Constraint(_, _) => unreachable!("Constraints are handled via custom builder"), } } } barrel-0.7.0/src/connectors.rs000064400000000000000000000013650000000000000143730ustar 00000000000000//! A module meant for library developers //! //! `barrel` can be used with different migration toolkits or //! SQL adapters. You can either use it to just generate strings //! or implemented the provided trait that will then automatically //! execute the SQL string on your apropriate database backend. //! //! You can then simple call `Migration::execute` to run the provided //! migration. /// A generic trait that frameworks using barrel can implement /// /// An object of this trait can be given to a `Migration` object to /// automatically generate and run the given SQL string for a /// database connection which is wrapped by it pub trait SqlRunner { /// Execute the migration on a backend fn execute>(&mut self, sql: S); } barrel-0.7.0/src/functions/mod.rs000064400000000000000000000004400000000000000147760ustar 00000000000000/// 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 } barrel-0.7.0/src/integrations/diesel.rs000064400000000000000000000135020000000000000161650ustar 00000000000000//! // This integration relies on _knowing_ which backend is being used at compile-time // This is a poor woman's XOR - if you know how to make it more pretty, PRs welcome <3 #[cfg(any( all(feature = "pg", feature = "mysql"), all(feature = "pg", feature = "sqlite3"), all(feature = "mysql", feature = "sqlite3") ))] compile_error!("`barrel` can only integrate with `diesel` if you select one (1) backend!"); use diesel_rs::connection::SimpleConnection; use diesel_rs::migration::{Migration, RunMigrationsError}; use std::fs::{self, File}; use std::io::prelude::*; use std::path::{Path, PathBuf}; use std::process::Command; /// Represents a migration run inside Diesel /// /// 1. Path /// 2. Version /// 3. Up /// 4. Down pub struct BarrelMigration(PathBuf, String, String, String); impl Migration for BarrelMigration { fn file_path(&self) -> Option<&Path> { Some(self.0.as_path()) } fn version(&self) -> &str { &self.1 } fn run(&self, conn: &SimpleConnection) -> Result<(), RunMigrationsError> { conn.batch_execute(&self.2)?; Ok(()) } fn revert(&self, conn: &SimpleConnection) -> Result<(), RunMigrationsError> { conn.batch_execute(&self.3)?; Ok(()) } } /// Generate migration files using the barrel schema builder pub fn generate_initial(path: &PathBuf) { generate_initial_with_content( path, &"fn up(migr: &mut Migration) {} \n\n".to_string(), &"fn down(migr: &mut Migration) {} \n".to_string(), ) } /// Generate migration files using the barrel schema builder with initial content pub fn generate_initial_with_content(path: &PathBuf, up_content: &String, down_content: &String) { let migr_path = path.join("mod.rs"); println!("Creating {}", migr_path.display()); let mut barrel_migr = fs::File::create(migr_path).unwrap(); barrel_migr.write(b"/// Handle up migrations \n").unwrap(); barrel_migr.write(up_content.as_bytes()).unwrap(); barrel_migr.write(b"/// Handle down migrations \n").unwrap(); barrel_migr.write(down_content.as_bytes()).unwrap(); } /// Generate a Migration from the provided path pub fn migration_from(path: &Path) -> Option> { match path.join("mod.rs").exists() { true => Some(run_barrel_migration_wrapper(&path.join("mod.rs"))), false => None, } } fn version_from_path(path: &Path) -> Result { path.parent() .unwrap_or_else(|| { panic!( "Migration doesn't appear to be in a directory: `{:?}`", path ) }) .file_name() .unwrap_or_else(|| panic!("Can't get file name from path `{:?}`", path)) .to_string_lossy() .split('_') .nth(0) .map(|s| Ok(s.replace('-', ""))) .unwrap_or_else(|| Err(())) } fn run_barrel_migration_wrapper(path: &Path) -> Box { let (up, down) = run_barrel_migration(&path); let version = version_from_path(path).unwrap(); let migration_path = match path.parent() { Some(parent_path) => parent_path.to_path_buf(), None => path.to_path_buf(), }; Box::new(BarrelMigration(migration_path, version, up, down)) } fn run_barrel_migration(migration: &Path) -> (String, String) { /* Create a tmp dir with src/ child */ use tempfile::Builder; let dir = Builder::new().prefix("barrel").tempdir().unwrap(); fs::create_dir_all(&dir.path().join("src")).unwrap(); let (feat, ident) = get_backend_pair(); let toml = format!( "# This file is auto generated by barrel [package] name = \"tmp-generator\" description = \"Doing nasty things with cargo\" version = \"0.0.0\" authors = [\"Katharina Fey \"] # TODO: Use same `barrel` dependency as crate [dependencies] barrel = {{ version = \"*\", features = [ {:?} ] }}", feat ); /* Add a Cargo.toml file */ let ct = dir.path().join("Cargo.toml"); let mut cargo_toml = File::create(&ct).unwrap(); cargo_toml.write_all(toml.as_bytes()).unwrap(); /* Generate main.rs based on user migration */ let main_file_path = &dir.path().join("src").join("main.rs"); let mut main_file = File::create(&main_file_path).unwrap(); let user_migration = migration.as_os_str().to_os_string().into_string().unwrap(); main_file .write_all( format!( "//! This file is auto generated by barrel extern crate barrel; use barrel::*; use barrel::backend::{ident}; include!(\"{}\"); fn main() {{ let mut m_up = Migration::new(); up(&mut m_up); println!(\"{{}}\", m_up.make::<{ident}>()); let mut m_down = Migration::new(); down(&mut m_down); println!(\"{{}}\", m_down.make::<{ident}>()); }} ", user_migration, ident = ident ) .as_bytes(), ) .unwrap(); let output = if cfg!(target_os = "windows") { Command::new("cargo") .current_dir(dir.path()) .arg("run") .output() .expect("failed to execute cargo!") } else { Command::new("sh") .current_dir(dir.path()) .arg("-c") .arg("cargo run") .output() .expect("failed to execute cargo!") }; let output = String::from_utf8_lossy(&output.stdout); let vec: Vec<&str> = output.split("\n").collect(); let up = String::from(vec[0]); let down = String::from(vec[1]); (up, down) } /// Uses the fact that barrel with diesel support is only compiled with _one_ feature /// /// The first string is the feature-name, the other the struct ident fn get_backend_pair() -> (&'static str, &'static str) { #[cfg(feature = "pg")] return ("pg", "Pg"); #[cfg(feature = "mysql")] return ("mysql", "Mysql"); #[cfg(feature = "sqlite3")] return ("sqlite3", "Sqlite"); } barrel-0.7.0/src/integrations/mod.rs000064400000000000000000000001640000000000000154770ustar 00000000000000//! Include external integrations into frameworks and libraries //! //! #[cfg(feature = "diesel")] pub mod diesel; barrel-0.7.0/src/lib.rs000064400000000000000000000142560000000000000127670ustar 00000000000000//! Powerful schema migration builder, that let's you write your SQL //! migrations in Rust. //! //! `barrel` makes writing migrations for different databases as easy //! as possible. It provides you with a common API over SQL, with //! certain features only provided for database specific //! implementations. This way you can focus on your Rust code, and //! stop worrying about SQL. //! //! `barrel` has three primary models: the //! [Migration](migration/struct.Migration.html) which represents all //! changes and changes made on a database level, the //! [Table](table/struct.Table.html) and the //! [Type](types/struct.Type.html). //! //! When creating or altering tables a lambda which exposes `&mut //! Table` is provided for initialisation. Adding columns is then as //! easy as calling `add_column(...)` on the table. //! //! Each column is statically typed and some types require some //! metadata in order to compile the migration (for example //! `Varchar(255)`). You can also provide default types and override //! encodings, nullability or uniqueness of columns. Some checks are //! performed at compile-time however most things (including) correct //! default values) are only checked at runtime. //! //! **Note** Since version `0.3.0` it is required to provide a //! database backend in order to compile `barrel`. //! //! The following code is a simple example of how to get going with //! `barrel` //! //! ```rust //! use barrel::{types, Migration}; //! //! fn main() { //! let mut m = Migration::new(); //! m.create_table("users", |t| { //! t.add_column("name", types::varchar(255)); //! t.add_column("age", types::integer()); //! t.add_column("owns_plushy_sharks", types::boolean()); //! }); //! } //! ``` //! //! `barrel` also supports more advanced types, such as `foreign(...)` //! and `array(...)` however currently doesn't support nested Array //! types on foreign keys (such as `array(array(foreign(...)))`). Each //! column addition returns a Column object which can then be used to //! provide further configuration. //! //! To generate SQL strings you have two options. If you just want to //! run the migration yourself simply run `Migration::exec()` where //! you provide a generic `SqlGenerator` type according to your //! database backend //! //! ```rust //! # #[cfg(feature = "pg")] //! # use barrel::backend::Pg; //! # use barrel::Migration; //! # let mut m = Migration::new(); //! // Example for pgsql //! # #[cfg(feature = "pg")] //! m.make::(); //! ``` //! //! Alternatively, if you're a library developer and you want to more //! easily embed `barrel` into your library you can simply implement //! the `DatabaseExecutor` trait for a type of yours that knows how to //! execute SQL. Running a migration with `barrel` is then super //! easy. //! //! ```rust //! use barrel::connectors::SqlRunner; //! # use barrel::Migration; //! # #[cfg(feature = "pg")] //! # use barrel::backend::Pg; //! //! struct MyRunner; //! impl SqlRunner for MyRunner { //! fn execute>(&mut self, sql: S) { //! # let s: String = sql.into(); //! // ... //! } //! } //! //! # let mut m = Migration::new(); //! # let mut executor = MyRunner; //! # #[cfg(feature = "pg")] //! m.execute::(&mut executor); //! ``` //! //! In this case `executor` is your provided type which implements the //! required trait. You can read more about this in the //! [connectors](connectors/index.html) module docs. //! //! If you find database-specific features or documentation lacking, //! don't hesitate to open an issue/PR about it. #[cfg(feature = "diesel")] pub mod integrations; #[cfg(feature = "diesel")] pub use integrations::*; pub mod backend; pub mod connectors; pub mod functions; pub mod migration; pub mod table; pub mod types; pub use backend::SqlVariant; pub use migration::Migration; pub use table::{Table, TableMeta}; #[cfg(test)] mod tests; use std::rc::Rc; /// An enum set that represents a single change on a table #[derive(Clone)] pub enum TableChange { /// Add a column of a name and type AddColumn(String, types::Type), /// Change an existing column ChangeColumn(String, types::Type, Rc), /// Simply rename a column RenameColumn(String, String), /// Remove a column DropColumn(String), /// Add some custom SQL if all else fails CustomLine(String), } /// An enum set that represents a single change on a database #[derive(Clone)] pub enum DatabaseChange { /// Create a new table CreateTable(Table, Rc), /// Create a new table *only* if it doesn't exist yet CreateTableIfNotExists(Table, Rc), /// Change fields on an existing table ChangeTable(Table, Rc), /// Rename a table RenameTable(String, String), /// Drop an existing table DropTable(String), /// Only drop a table if it exists DropTableIfExists(String), /// Add some custom SQL if all else fails CustomLine(String), } /// An enum set that represents operations done with and on indices #[derive(Clone)] pub enum IndexChange { /// Add a multi-column index AddIndex { index: String, table: String, 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), } /// An enum set that represents operations done with and on constraints #[derive(Clone)] pub enum ConstraintChange { /// Add a new constraint AddConstraint { index: String, columns: types::Type, // Should always be a `Constraint` type }, } /// An enum set that represents operations done with and on foreign keys #[derive(Clone)] pub enum ForeignKeyChange { /// Add a foreign key AddForeignKey { columns: Vec, table: String, relation_columns: Vec, }, } /// 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), } barrel-0.7.0/src/migration.rs000064400000000000000000000231230000000000000142030ustar 00000000000000//! Core migration creation handler //! //! A migration can be done for a specific schema which contains //! multiple additions or removables from a database or table. //! //! At the end of crafting a migration you can use `Migration::exec` to //! get the raw SQL string for a database backend or `Migration::revert` //! to try to auto-infer the migration rollback. In cases where that //! can't be done the `Result` will not unwrap. //! //! You can also use `Migration::exec` with your SQL connection for convenience //! if you're a library developer. use crate::table::{Table, TableMeta}; use crate::DatabaseChange; use crate::backend::{SqlGenerator, SqlVariant}; use crate::connectors::SqlRunner; use std::rc::Rc; /// Represents a schema migration on a database pub struct Migration { #[doc(hidden)] pub schema: Option, #[doc(hidden)] pub changes: Vec, } impl Migration { pub fn new() -> Migration { Migration { schema: None, changes: Vec::new(), } } /// Specify a database schema name for this migration pub fn schema>(self, schema: S) -> Migration { Self { schema: Some(schema.into()), ..self } } /// Creates the SQL for this migration for a specific backend /// /// This function copies state and does not touch the original /// migration layout. This allows you to call `revert` later on /// in the process to auto-infer the down-behaviour pub fn make(&self) -> String { use DatabaseChange::*; /* What happens in make, stays in make (sort of) */ let mut changes = self.changes.clone(); let schema = self.schema.as_ref().map(|s| s.as_str()); changes.iter_mut().fold(String::new(), |mut sql, change| { match change { &mut CreateTable(ref mut t, ref mut cb) | &mut CreateTableIfNotExists(ref mut t, ref mut cb) => { cb(t); // Run the user code let sql_changes = t.make::(false, schema); let name = t.meta.name().clone(); sql.push_str(&match change { CreateTable(_, _) => T::create_table(&name, schema), CreateTableIfNotExists(_, _) => { T::create_table_if_not_exists(&name, schema) } _ => unreachable!(), }); sql.push_str(" ("); let l = sql_changes.columns.len(); for (i, slice) in sql_changes.columns.iter().enumerate() { sql.push_str(slice); if i < l - 1 { sql.push_str(", "); } } let l = sql_changes.constraints.len(); for (i, slice) in sql_changes.constraints.iter().enumerate() { if sql_changes.columns.len() > 0 && i == 0 { sql.push_str(", ") } sql.push_str(slice); if i < l - 1 { sql.push_str(", ") } } 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(", ") } sql.push_str(slice); if i < l - 1 { sql.push_str(", ") } } sql.push_str(")"); // Add additional index columns if sql_changes.indices.len() > 0 { sql.push_str(";"); sql.push_str(&sql_changes.indices.join(";")); } } &mut DropTable(ref name) => sql.push_str(&T::drop_table(name, schema)), &mut DropTableIfExists(ref name) => { sql.push_str(&T::drop_table_if_exists(name, schema)) } &mut RenameTable(ref old, ref new) => { sql.push_str(&T::rename_table(old, new, schema)) } &mut ChangeTable(ref mut t, ref mut cb) => { cb(t); let sql_changes = t.make::(true, schema); sql.push_str(&T::alter_table(&t.meta.name(), schema)); sql.push_str(" "); let l = sql_changes.columns.len(); for (i, slice) in sql_changes.columns.iter().enumerate() { sql.push_str(slice); if i < l - 1 { sql.push_str(", "); } } 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(", ") } sql.push_str("ADD "); sql.push_str(slice); if i < l - 1 { sql.push_str(", ") } } 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 sql_changes.indices.len() > 0 { sql.push_str(";"); sql.push_str(&sql_changes.indices.join(";")); } } &mut CustomLine(ref line) => sql.push_str(line.as_str()), } sql.push_str(";"); sql }) } /// The same as `make` but making a run-time check for sql variant /// /// The `SqlVariant` type is populated based on the backends /// that are being selected at compile-time. /// /// This function panics if the provided variant is empty! pub fn make_from(&self, variant: SqlVariant) -> String { variant.run_for(self) } /// Inject a line of custom SQL into the top-level migration scope /// /// This is a bypass to the barrel typesystem, in case there is /// something your database supports that barrel doesn't, or if /// there is an issue with the way that barrel represents types. /// It does however mean that the SQL provided needs to be /// specific for one database, meaning that future migrations /// might become cumbersome. pub fn inject_custom>(&mut self, sql: S) { self.changes.push(DatabaseChange::CustomLine(sql.into())); } /// Automatically infer the `down` step of this migration /// /// Will thrown an error if behaviour is ambiguous or not /// possible to infer (e.g. revert a `drop_table`) pub fn revert(&self) -> String { unimplemented!() } /// Pass a reference to a migration toolkit runner which will /// automatically generate and execute pub fn execute(&self, runner: &mut T) { runner.execute(self.make::()); } /// Create a new table with a specific name pub fn create_table, F: 'static>(&mut self, name: S, cb: F) -> &mut TableMeta where F: Fn(&mut Table), { self.changes .push(DatabaseChange::CreateTable(Table::new(name), Rc::new(cb))); match self.changes.last_mut().unwrap() { &mut DatabaseChange::CreateTable(ref mut t, _) => &mut t.meta, _ => unreachable!(), } } /// Create a new table *only* if it doesn't exist yet pub fn create_table_if_not_exists, F: 'static>( &mut self, name: S, cb: F, ) -> &mut TableMeta where F: Fn(&mut Table), { self.changes.push(DatabaseChange::CreateTableIfNotExists( Table::new(name), Rc::new(cb), )); match self.changes.last_mut().unwrap() { &mut DatabaseChange::CreateTableIfNotExists(ref mut t, _) => &mut t.meta, _ => unreachable!(), } } /// Change fields on an existing table pub fn change_table, F: 'static>(&mut self, name: S, cb: F) where F: Fn(&mut Table), { let t = Table::new(name); let c = DatabaseChange::ChangeTable(t, Rc::new(cb)); self.changes.push(c); } /// Rename a table pub fn rename_table>(&mut self, old: S, new: S) { self.changes .push(DatabaseChange::RenameTable(old.into(), new.into())); } /// Drop an existing table pub fn drop_table>(&mut self, name: S) { self.changes.push(DatabaseChange::DropTable(name.into())); } /// Only drop a table if it exists pub fn drop_table_if_exists>(&mut self, name: S) { self.changes .push(DatabaseChange::DropTableIfExists(name.into())); } } barrel-0.7.0/src/schema.rs000064400000000000000000000011300000000000000134440ustar 00000000000000//! Simple schema representation for migration state trait Schemas { /// Returns the name of a database fn name(&self) -> String; /// Returns a list of all tables in a database fn tables(&self) -> Vec; /// Returns a list of all column names and types fn columns(&self, table: &str) -> Vec<(String, Column)>; } trait Column { /// Get the type of column in SQL specific terms fn type(&self) -> String; } // Describe the current state of a database to apply a migration to struct Schema { db_name: String, columns: Vec, } impl Schema { } barrel-0.7.0/src/table.rs000064400000000000000000000220640000000000000133040ustar 00000000000000//! A module that represents tables and columns //! //! A table is a collection of columns and some metadata. Creating //! a table gives you access to the metadata fields that can only //! be set when creating the table. //! //! You can also change existing tables with a closure that can //! then access individual columns in that table. use super::{ backend::SqlGenerator, ConstraintChange, ForeignKeyChange, IndexChange, PrimaryKeyChange, TableChange, }; use crate::types::Type; use std::fmt::{Debug, Formatter, Result as FmtResult}; impl Debug for TableChange { fn fmt(&self, f: &mut Formatter) -> FmtResult { f.write_str("TableChange") } } impl Debug for IndexChange { fn fmt(&self, f: &mut Formatter) -> FmtResult { f.write_str("IndexChange") } } impl Debug for ConstraintChange { fn fmt(&self, f: &mut Formatter) -> FmtResult { f.write_str("ConstraintChange") } } impl Debug for ForeignKeyChange { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { f.write_str("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, indices: Vec, constraints: Vec, foreign_keys: Vec, primary_key: Option, } #[derive(Debug, Clone)] pub struct SqlChanges { pub(crate) columns: Vec, pub(crate) indices: Vec, pub(crate) constraints: Vec, pub(crate) foreign_keys: Vec, pub(crate) primary_key: Option, } impl Table { pub fn new>(name: S) -> Self { Self { meta: TableMeta::new(name.into()), columns: vec![], indices: vec![], constraints: vec![], foreign_keys: vec![], primary_key: None, } } /// Add a new column to a table /// /// ```rust /// # use barrel::{types, Migration}; /// # let mut m = Migration::new(); /// # m.create_table("users", |table| { /// table.add_column("id", types::primary()); /// table.add_column("name", types::varchar(64)); /// # }); /// ``` pub fn add_column>(&mut self, name: S, _type: Type) -> &mut Type { self.columns .push(TableChange::AddColumn(name.into(), _type)); match self.columns.last_mut().unwrap() { &mut TableChange::AddColumn(_, ref mut c) => c, _ => unreachable!(), } } pub fn drop_column>(&mut self, name: S) { self.columns.push(TableChange::DropColumn(name.into())); } pub fn rename_column>(&mut self, old: S, new: S) { self.columns .push(TableChange::RenameColumn(old.into(), new.into())); } /// Inject a line of custom SQL into the table block /// /// This is a bypass to the barrel typesystem, in case there is /// something your database supports that barrel doesn't, or if /// there is an issue with the way that barrel represents types. /// It does however mean that the SQL provided needs to be /// specific for one database, meaning that future migrations /// might become cumbersome. pub fn inject_custom>(&mut self, sql: S) { self.columns.push(TableChange::CustomLine(sql.into())); } /// Add a new index to a table, spanning over multiple columns pub fn add_index>(&mut self, name: S, columns: Type) { match columns.inner { crate::types::BaseType::Index(_) => {} _ => panic!("Calling `add_index` with a non-`Index` type is not allowed!"), } self.indices.push(IndexChange::AddIndex { table: self.meta.name.clone(), index: name.into(), columns, }); } pub fn add_constraint>(&mut self, name: S, columns: Type) { match columns.inner { crate::types::BaseType::Constraint(_, _) => {} _ => panic!("Calling `add_index` with a non-`Constraint` type is not allowed!"), } self.constraints.push(ConstraintChange::AddConstraint { index: name.into(), columns, }); } pub fn add_partial_index>(&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>(&mut self, name: S) { self.indices.push(IndexChange::RemoveIndex( self.meta.name.clone(), name.into(), )); } 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, }) } 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(&mut self, ex: bool, schema: Option<&str>) -> SqlChanges { use ConstraintChange as CC; use ForeignKeyChange as KFC; use IndexChange as IC; use PrimaryKeyChange as PKC; use TableChange as TC; let columns = self .columns .iter_mut() .map(|change| match change { &mut TC::AddColumn(ref name, ref col) => T::add_column(ex, schema, name, &col), &mut TC::DropColumn(ref name) => T::drop_column(name), &mut TC::RenameColumn(ref old, ref new) => T::rename_column(old, new), &mut TC::ChangeColumn(ref mut name, _, _) => T::alter_table(name, schema), &mut TC::CustomLine(ref sql) => sql.clone(), }) .collect(); let indices = self .indices .iter() .map(|change| match change { IC::AddIndex { index, 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 constraints = self .constraints .iter() .map(|c| match c { CC::AddConstraint { index, columns } => T::create_constraint(index, columns), }) .collect(); 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(); SqlChanges { columns, indices, constraints, foreign_keys, primary_key, } } } /// Some metadata about a table that was just created #[derive(Debug, Clone)] pub struct TableMeta { pub name: String, pub encoding: String, } impl TableMeta { /// Create a new tablemeta with default values pub fn new(name: String) -> Self { Self { name, encoding: "utf-8".to_owned(), } } /// Get a clone of the table name pub fn name(&self) -> String { self.name.clone() } /// Specify an encoding for this table which might vary from the main encoding /// of your database pub fn encoding>(&mut self, enc: S) -> &mut TableMeta { self.encoding = enc.into(); self } } barrel-0.7.0/src/tests/common/cloning.rs000064400000000000000000000042730000000000000163020ustar 00000000000000use crate::{ types::{self, Type}, Migration, }; use std::fmt; #[derive(PartialEq, Clone, Copy)] pub enum DataTypes { Bool, F64, I64, String, } impl DataTypes { #[allow(unused)] pub fn string(&self) -> &str { match *self { DataTypes::Bool => "bool", DataTypes::F64 => "f64", DataTypes::I64 => "i64", DataTypes::String => "String", } } pub fn to_database_type(&self) -> Type { match *self { DataTypes::Bool => types::text(), DataTypes::F64 => types::double(), DataTypes::I64 => types::integer(), DataTypes::String => types::text(), } } } impl fmt::Debug for DataTypes { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let printable = match *self { DataTypes::Bool => "", DataTypes::F64 => "f64", DataTypes::I64 => "i64", DataTypes::String => "string", }; write!(f, "{:#?}", printable) } } #[derive(Clone)] pub struct ColumnDef { pub name: String, pub data_type: DataTypes, } impl ColumnDef { pub fn new(name: String, data_type: DataTypes) -> ColumnDef { ColumnDef { name: name, data_type: data_type, } } } impl fmt::Debug for ColumnDef { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}: {:?}", self.name, self.data_type) } } pub fn create_table_if_not_exists(name: &str, columns: &Vec) { let mut m = Migration::new(); let cols = columns.clone(); m.create_table(name, move |t| { for cd in &cols { let cname: &str = &cd.name; t.add_column(cname, cd.data_type.to_database_type()); } }); } #[test] fn barrel_reverse_integration() { let cols = vec![ ColumnDef::new("name".into(), DataTypes::String), ColumnDef::new("age".into(), DataTypes::I64), ColumnDef::new("coolness".into(), DataTypes::F64), ColumnDef::new("plushy_sharks_owned".into(), DataTypes::Bool), ]; // We just call this function and hope it doesn't panic create_table_if_not_exists("users", &cols); } barrel-0.7.0/src/tests/common/mod.rs000064400000000000000000000061470000000000000154320ustar 00000000000000/* Include some external tests */ mod cloning; mod utils; #[cfg(all(feature = "sqlite3", feature = "pg", feature = "mysql"))] mod runtime; use crate::types::{BaseType, Type, WrappedDefault}; use crate::Migration; #[test] fn create_multiple_tables() { let mut migr = Migration::new(); migr.create_table("foo", |_| {}); migr.create_table("bar", |_| {}); assert!(migr.changes.len() == 2); } #[test] fn create_table_if_not_exists() { let mut migr = Migration::new(); migr.create_table_if_not_exists("foo", |_| {}); assert!(migr.changes.len() == 1); } #[test] fn pin_public_api() { // The best sql type because it's very queer 🏳️‍🌈 let tt = Type::new(BaseType::Custom("GAY")); assert_eq!(tt.nullable, false); assert_eq!(tt.indexed, false); assert_eq!(tt.unique, false); assert_eq!(tt.increments, false); assert_eq!(tt.default, None); assert_eq!(tt.size, None); assert_eq!(tt.inner, BaseType::Custom("GAY")); } #[test] fn pin_struct_layout() { // The best sql type because it's very queer 🏳️‍🌈 let tt = Type { nullable: false, indexed: false, primary: false, unique: false, increments: false, default: None, size: None, inner: BaseType::Custom("GAY"), }; assert_eq!(tt.nullable, false); assert_eq!(tt.indexed, false); assert_eq!(tt.primary, false); assert_eq!(tt.unique, false); assert_eq!(tt.increments, false); assert_eq!(tt.default, None); assert_eq!(tt.size, None); assert_eq!(tt.inner, BaseType::Custom("GAY")); } #[test] fn default_render_anytext() { use self::WrappedDefault::*; assert_eq!(format!("{}", AnyText("hello".into())), "hello".to_owned()); } #[test] fn default_render_integer() { use self::WrappedDefault::*; assert_eq!(format!("{}", Integer(42)), "42".to_owned()); } #[test] fn default_render_float() { use self::WrappedDefault::*; assert_eq!(format!("{}", Float(42000.0)), "42000".to_owned()); } #[test] fn default_render_double() { use self::WrappedDefault::*; assert_eq!( format!("{}", Double(123456789.123456789)), "123456789.12345679".to_owned() ); } #[test] fn default_render_uuid() { use self::WrappedDefault::*; assert_eq!( format!("{}", UUID("b616ab2a-e13c-11e8-9f32-f2801f1b9fd1".into())), "b616ab2a-e13c-11e8-9f32-f2801f1b9fd1".to_owned() ); } // #[test] // fn default_render_date() { // use self::WrappedDefault::*; // assert_eq!(format!("{}", Date(SystemTime::now())), "".to_owned()); // } #[test] fn default_render_binary() { use self::WrappedDefault::*; assert_eq!( format!( "{}", Binary(&[ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ]) ), "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]".to_owned() ); } // #[test] // fn default_render_array() { // use self::WrappedDefault::*; // assert_eq!( // format!("{}", Array(vec![Type::new(BaseType::Custom("GAY"))])), // "".to_owned() // ); // } barrel-0.7.0/src/tests/common/runtime.rs000064400000000000000000000010550000000000000163270ustar 00000000000000//! These tests check any kind of runtime-check behaviour //! //! They depend on all backends mostly for simplicity. use crate::{types, Migration, SqlVariant}; /// This test mostly exists to see if we panic #[test] fn generate_from() { let mut m = Migration::new(); m.create_table("testing", |table| { table.add_column("id", types::primary()); table.add_column("name", types::varchar(64)); }); let _ = m.make_from(SqlVariant::Pg); let _ = m.make_from(SqlVariant::Mysql); let _ = m.make_from(SqlVariant::Sqlite); } barrel-0.7.0/src/tests/common/utils.rs000064400000000000000000000004170000000000000160050ustar 00000000000000use crate::types; #[test] fn cloning_types() { let tt = types::text(); assert_eq!(tt, tt.clone()); } #[test] fn equals_types() { let t1 = types::text(); let t2 = t1.clone(); let t3 = types::integer(); assert!(t1 == t2); assert!(t1 != t3); } barrel-0.7.0/src/tests/mod.rs000064400000000000000000000003600000000000000141310ustar 00000000000000//! A unit testing module for barrel // We can always trust these tests 👍 mod common; #[cfg(feature = "mysql")] mod mysql; #[cfg(feature = "pg")] mod pg; #[cfg(feature = "sqlite3")] mod sqlite3; #[cfg(feature = "mssql")] mod mssql; barrel-0.7.0/src/tests/mssql/add_column.rs000064400000000000000000000034620000000000000166240ustar 00000000000000//! All add_column combinations for pgsql #![allow(unused_imports)] use crate::backend::{MsSql, SqlGenerator}; use crate::types; #[test] fn text() { let sql = MsSql::add_column(true, None, "Text", &types::text()); assert_eq!(String::from("ADD [Text] TEXT NOT NULL"), sql); } #[test] fn varchar() { let sql = MsSql::add_column(true, None, "Varchar", &types::varchar(255)); assert_eq!(String::from("ADD [Varchar] VARCHAR(255) NOT NULL"), sql); } #[test] fn integer() { let sql = MsSql::add_column(true, None, "Integer", &types::integer()); assert_eq!(String::from("ADD [Integer] INT NOT NULL"), sql); } #[test] fn float() { let sql = MsSql::add_column(true, None, "Float", &types::float()); assert_eq!(String::from("ADD [Float] FLOAT(24) NOT NULL"), sql); } #[test] fn double() { let sql = MsSql::add_column(true, None, "Double", &types::double()); assert_eq!(String::from("ADD [Double] FLOAT(53) NOT NULL"), sql); } #[test] fn boolean() { let sql = MsSql::add_column(true, None, "Boolean", &types::boolean()); assert_eq!(String::from("ADD [Boolean] BIT NOT NULL"), sql); } #[test] fn binary() { let sql = MsSql::add_column(true, None, "Binary", &types::binary()); assert_eq!(String::from("ADD [Binary] VARBINARY(MAX) NOT NULL"), sql); } #[test] fn date() { let sql = MsSql::add_column(true, None, "Date", &types::date()); assert_eq!(String::from("ADD [Date] DATE NOT NULL"), sql); } #[test] fn foreign() { let sql = MsSql::add_column(true, None, "Foreign", &types::foreign("posts", "id")); assert_eq!( String::from("ADD [Foreign] INT REFERENCES [posts]([id]) NOT NULL"), sql ); } #[test] fn custom() { let sql = MsSql::add_column(true, None, "Xml", &types::custom("XML")); assert_eq!(String::from("ADD [Xml] XML NOT NULL"), sql); } barrel-0.7.0/src/tests/mssql/create_table.rs000064400000000000000000000066520000000000000171350ustar 00000000000000//! Some unit tests that create create tables #![allow(unused_imports)] use crate::backend::{MsSql, SqlGenerator}; use crate::{types, Migration, Table}; #[test] fn create_table_if_not_exists_doesnt_hit_unreachable() { let mut m = Migration::new(); m.create_table_if_not_exists("artist", |t| { t.add_column("id", types::primary()); t.add_column("name", types::text().nullable(true)); t.add_column("description", types::text().nullable(true)); t.add_column("pic", types::text().nullable(true)); t.add_column("mbid", types::text().nullable(true)); }); assert_eq!(m.make::(), String::from("IF NOT EXISTS (SELECT * FROM sys.tables WHERE name='artist') CREATE TABLE [artist] ([id] INT IDENTITY(1,1) PRIMARY KEY NOT NULL, [name] TEXT, [description] TEXT, [pic] TEXT, [mbid] TEXT);")); } #[test] fn basic_fields() { let mut m = Migration::new(); m.create_table("users", |t: &mut Table| { t.add_column("id", types::primary()); t.add_column("name", types::varchar(255)); t.add_column("age", types::integer()); t.add_column("plushy_sharks_owned", types::boolean()); }); assert_eq!( m.make::(), String::from("CREATE TABLE [users] ([id] INT IDENTITY(1,1) PRIMARY KEY NOT NULL, [name] VARCHAR(255) NOT NULL, [age] INT NOT NULL, [plushy_sharks_owned] BIT NOT NULL);") ); } #[test] fn basic_fields_nullable() { let mut m = Migration::new(); m.create_table("users", |t: &mut Table| { t.add_column("id", types::primary()); t.add_column("name", types::varchar(255).nullable(true)); t.add_column("age", types::integer().nullable(true)); t.add_column("plushy_sharks_owned", types::boolean().nullable(true)); }); assert_eq!( m.make::(), String::from("CREATE TABLE [users] ([id] INT IDENTITY(1,1) PRIMARY KEY NOT NULL, [name] VARCHAR(255), [age] INT, [plushy_sharks_owned] BIT);") ); } #[test] fn create_multiple_tables() { let mut m = Migration::new(); m.create_table("artist", |t| { t.add_column("id", types::primary()); t.add_column("name", types::text()); t.add_column("description", types::text()); t.add_column("pic", types::text()); t.add_column("mbid", types::text()); }); m.create_table("album", |t| { t.add_column("id", types::primary()); t.add_column("name", types::text()); t.add_column("pic", types::text()); t.add_column("mbid", types::text()); }); assert_eq!(m.make::(), String::from("CREATE TABLE [artist] ([id] INT IDENTITY(1,1) PRIMARY KEY NOT NULL, [name] TEXT NOT NULL, [description] TEXT NOT NULL, [pic] TEXT NOT NULL, [mbid] TEXT NOT NULL);CREATE TABLE [album] ([id] INT IDENTITY(1,1) PRIMARY KEY NOT NULL, [name] TEXT NOT NULL, [pic] TEXT NOT NULL, [mbid] TEXT NOT NULL);")); } #[test] fn drop_table() { let mut m = Migration::new(); m.drop_table("users"); assert_eq!(m.make::(), String::from("DROP TABLE [users];")); } #[test] fn drop_table_if_exists() { let mut m = Migration::new(); m.drop_table_if_exists("users"); assert_eq!( m.make::(), String::from("DROP TABLE IF EXISTS [users];") ); } #[test] fn rename_table() { let mut m = Migration::new(); m.rename_table("users", "cool_users"); assert_eq!( m.make::(), String::from("EXEC sp_rename 'users', 'cool_users';") ); } barrel-0.7.0/src/tests/mssql/mod.rs000064400000000000000000000001300000000000000152630ustar 00000000000000//! Test pgsql generation mod add_column; mod create_table; mod reference; mod simple; barrel-0.7.0/src/tests/mssql/reference.rs000064400000000000000000000012420000000000000164470ustar 00000000000000#![allow(unused_imports)] use crate::backend::{MsSql, SqlGenerator}; use crate::{types, Migration, Table}; #[test] fn in_schema() { let sql = MsSql::add_column( false, Some("schema"), "author", &types::foreign("users", "id"), ); assert_eq!( sql, "[author] INT REFERENCES [schema].[users]([id]) NOT NULL" ); } #[test] fn ext_schema() { let sql = MsSql::add_column( false, Some("schema"), "author", &types::foreign_schema("other_schema", "users", "id"), ); assert_eq!( sql, "[author] INT REFERENCES [other_schema].[users]([id]) NOT NULL" ); } barrel-0.7.0/src/tests/mssql/simple.rs000064400000000000000000000033670000000000000160140ustar 00000000000000//! Other simple table/ column migrations #![allow(unused_imports)] use crate::backend::{MsSql, SqlGenerator}; #[test] fn create_table() { let sql = MsSql::create_table("table_to_create", None); assert_eq!(String::from("CREATE TABLE [table_to_create]"), sql); } #[test] fn create_table_with_schema() { let sql = MsSql::create_table("table_to_create", Some("my_schema")); assert_eq!( String::from("CREATE TABLE [my_schema].[table_to_create]"), sql ); } #[test] fn create_table_if_not_exists() { let sql = MsSql::create_table_if_not_exists("table_to_create", None); assert_eq!(String::from("IF NOT EXISTS (SELECT * FROM sys.tables WHERE name=\'table_to_create\') CREATE TABLE [table_to_create]"), sql); } #[test] fn drop_table() { let sql = MsSql::drop_table("table_to_drop", None); assert_eq!(String::from("DROP TABLE [table_to_drop]"), sql); } #[test] fn drop_table_if_exists() { let sql = MsSql::drop_table_if_exists("table_to_drop", None); assert_eq!(String::from("DROP TABLE IF EXISTS [table_to_drop]"), sql); } #[test] fn rename_table() { let sql = MsSql::rename_table("old_table", "new_table", None); assert_eq!(String::from("EXEC sp_rename 'old_table', 'new_table'"), sql); } #[test] fn alter_table() { let sql = MsSql::alter_table("table_to_alter", None); assert_eq!(String::from("ALTER TABLE [table_to_alter]"), sql); } #[test] fn drop_column() { let sql = MsSql::drop_column("column_to_drop"); assert_eq!(String::from("DROP COLUMN [column_to_drop]"), sql); } #[test] fn rename_column() { let sql = MsSql::rename_column("table.old_column", "table.new_column"); assert_eq!( String::from("EXEC sp_rename 'table.old_column', 'table.new_column'"), sql ); } barrel-0.7.0/src/tests/mysql/add_column.rs000064400000000000000000000033360000000000000166320ustar 00000000000000//! All add_column combinations for mysql #![allow(unused_imports)] use crate::backend::{MySql, SqlGenerator}; use crate::types; #[test] fn text() { let sql = MySql::add_column(true, None, "Text", &types::text()); assert_eq!(String::from("ADD COLUMN `Text` TEXT NOT NULL"), sql); } #[test] fn varchar() { let sql = MySql::add_column(true, None, "Varchar", &types::varchar(255)); assert_eq!( String::from("ADD COLUMN `Varchar` VARCHAR(255) NOT NULL"), sql ); } #[test] fn integer() { let sql = MySql::add_column(true, None, "Integer", &types::integer()); assert_eq!(String::from("ADD COLUMN `Integer` INTEGER NOT NULL"), sql); } #[test] fn float() { let sql = MySql::add_column(true, None, "Float", &types::float()); assert_eq!(String::from("ADD COLUMN `Float` FLOAT NOT NULL"), sql); } #[test] fn double() { let sql = MySql::add_column(true, None, "Double", &types::double()); assert_eq!(String::from("ADD COLUMN `Double` DOUBLE NOT NULL"), sql); } #[test] fn boolean() { let sql = MySql::add_column(true, None, "Boolean", &types::boolean()); assert_eq!(String::from("ADD COLUMN `Boolean` BOOLEAN NOT NULL"), sql); } #[test] fn binary() { let sql = MySql::add_column(true, None, "Binary", &types::binary()); assert_eq!(String::from("ADD COLUMN `Binary` BYTEA NOT NULL"), sql); } #[test] fn date() { let sql = MySql::add_column(true, None, "Date", &types::date()); assert_eq!(String::from("ADD COLUMN `Date` DATE NOT NULL"), sql); } #[test] fn foreign() { let sql = MySql::add_column(true, None, "Foreign", &types::foreign("posts", "id")); assert_eq!( String::from("ADD COLUMN `Foreign` INTEGER REFERENCES posts(id) NOT NULL"), sql ); } barrel-0.7.0/src/tests/mysql/create_table.rs000064400000000000000000000033560000000000000171410ustar 00000000000000//! Some unit tests that create create tables #![allow(unused_imports)] use crate::backend::{MySql, SqlGenerator}; use crate::{types, Migration, Table}; #[test] fn create_multiple_tables() { let mut m = Migration::new(); m.create_table("artist", |t| { t.add_column("id", types::primary()); t.add_column("name", types::text().nullable(true)); t.add_column("description", types::text().nullable(true)); t.add_column("pic", types::text().nullable(true)); t.add_column("mbid", types::text().nullable(true)); }); m.create_table("album", |t| { t.add_column("id", types::primary()); t.add_column("name", types::text().nullable(true)); t.add_column("pic", types::text().nullable(true)); t.add_column("mbid", types::text().nullable(true)); }); assert_eq!(m.make::(), String::from("CREATE TABLE `artist` (`id` INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` TEXT, `description` TEXT, `pic` TEXT, `mbid` TEXT);CREATE TABLE `album` (`id` INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` TEXT, `pic` TEXT, `mbid` TEXT);")); } #[test] fn create_table_if_not_exists_doesnt_hit_unreachable() { let mut m = Migration::new(); m.create_table_if_not_exists("artist", |t| { t.add_column("id", types::primary()); t.add_column("name", types::text().nullable(true)); t.add_column("description", types::text().nullable(true)); t.add_column("pic", types::text().nullable(true)); t.add_column("mbid", types::text().nullable(true)); }); assert_eq!(m.make::(), String::from("CREATE TABLE IF NOT EXISTS `artist` (`id` INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` TEXT, `description` TEXT, `pic` TEXT, `mbid` TEXT);")); } barrel-0.7.0/src/tests/mysql/mod.rs000064400000000000000000000001110000000000000152700ustar 00000000000000//! Test mysql generation mod add_column; mod create_table; mod simple; barrel-0.7.0/src/tests/mysql/simple.rs000064400000000000000000000025410000000000000160130ustar 00000000000000//! Other simple table/ column migrations #![allow(unused_imports)] use crate::backend::{MySql, SqlGenerator}; #[test] fn create_table() { let sql = MySql::create_table("table_to_create", None); assert_eq!(String::from("CREATE TABLE `table_to_create`"), sql); } #[test] fn create_table_with_schema() { let sql = MySql::create_table("table_to_create", Some("my_schema")); assert_eq!( String::from("CREATE TABLE `my_schema`.`table_to_create`"), sql ); } #[test] fn create_table_if_not_exists() { let sql = MySql::create_table_if_not_exists("table_to_create", None); assert_eq!( String::from("CREATE TABLE IF NOT EXISTS `table_to_create`"), sql ); } #[test] fn drop_table() { let sql = MySql::drop_table("table_to_drop", None); assert_eq!(String::from("DROP TABLE `table_to_drop`"), sql); } #[test] fn drop_table_if_exists() { let sql = MySql::drop_table_if_exists("table_to_drop", None); assert_eq!(String::from("DROP TABLE IF EXISTS `table_to_drop`"), sql); } #[test] fn rename_table() { let sql = MySql::rename_table("old_table", "new_table", None); assert_eq!(String::from("RENAME TABLE `old_table` TO `new_table`"), sql); } #[test] fn alter_table() { let sql = MySql::alter_table("table_to_alter", None); assert_eq!(String::from("ALTER TABLE `table_to_alter`"), sql); } barrel-0.7.0/src/tests/pg/add_column.rs000064400000000000000000000104360000000000000160720ustar 00000000000000//! All add_column combinations for pgsql #![allow(unused_imports)] use crate::backend::{Pg, SqlGenerator}; use crate::types; #[test] fn text() { let sql = Pg::add_column(true, None, "Text", &types::text()); assert_eq!(String::from("ADD COLUMN \"Text\" TEXT NOT NULL"), sql); } #[test] fn varchar() { let sql = Pg::add_column(true, None, "Varchar", &types::varchar(255)); assert_eq!( String::from("ADD COLUMN \"Varchar\" VARCHAR(255) NOT NULL"), sql ); } #[test] fn integer() { let sql = Pg::add_column(true, None, "Integer", &types::integer()); assert_eq!(String::from("ADD COLUMN \"Integer\" INTEGER NOT NULL"), sql); } #[test] fn float() { let sql = Pg::add_column(true, None, "Float", &types::float()); assert_eq!(String::from("ADD COLUMN \"Float\" FLOAT NOT NULL"), sql); } #[test] fn double() { let sql = Pg::add_column(true, None, "Double", &types::double()); assert_eq!( String::from("ADD COLUMN \"Double\" DOUBLE PRECISION NOT NULL"), sql ); } #[test] fn boolean() { let sql = Pg::add_column(true, None, "Boolean", &types::boolean()); assert_eq!(String::from("ADD COLUMN \"Boolean\" BOOLEAN NOT NULL"), sql); } #[test] fn binary() { let sql = Pg::add_column(true, None, "Binary", &types::binary()); assert_eq!(String::from("ADD COLUMN \"Binary\" BYTEA NOT NULL"), sql); } #[test] fn date() { let sql = Pg::add_column(true, None, "Date", &types::date()); assert_eq!(String::from("ADD COLUMN \"Date\" DATE NOT NULL"), sql); } #[test] fn foreign() { let sql = Pg::add_column(true, None, "Foreign", &types::foreign("posts", "id")); assert_eq!( String::from("ADD COLUMN \"Foreign\" INTEGER REFERENCES \"posts\"(id) NOT NULL"), sql ); } #[test] fn custom() { let sql = Pg::add_column(true, None, "Point", &types::custom("POINT")); assert_eq!(String::from("ADD COLUMN \"Point\" POINT NOT NULL"), sql); } #[test] fn array_text() { let sql = Pg::add_column(true, None, "Array of Text", &types::array(&types::text())); assert_eq!( String::from("ADD COLUMN \"Array of Text\" TEXT[] NOT NULL"), sql ); } #[test] fn array_varchar() { let sql = Pg::add_column( true, None, "Array of Varchar", &types::array(&types::varchar(255)), ); assert_eq!( String::from("ADD COLUMN \"Array of Varchar\" VARCHAR(255)[] NOT NULL"), sql ); } #[test] fn array_integer() { let sql = Pg::add_column( true, None, "Array of Integer", &types::array(&types::integer()), ); assert_eq!( String::from("ADD COLUMN \"Array of Integer\" INTEGER[] NOT NULL"), sql ); } #[test] fn array_float() { let sql = Pg::add_column(true, None, "Array of Float", &types::array(&types::float())); assert_eq!( String::from("ADD COLUMN \"Array of Float\" FLOAT[] NOT NULL"), sql ); } #[test] fn array_double() { let sql = Pg::add_column( true, None, "Array of Double", &types::array(&types::double()), ); assert_eq!( String::from("ADD COLUMN \"Array of Double\" DOUBLE PRECISION[] NOT NULL"), sql ); } #[test] fn array_boolean() { let sql = Pg::add_column( true, None, "Array of Boolean", &types::array(&types::boolean()), ); assert_eq!( String::from("ADD COLUMN \"Array of Boolean\" BOOLEAN[] NOT NULL"), sql ); } #[test] fn array_binary() { let sql = Pg::add_column( true, None, "Array of Binary", &types::array(&types::binary()), ); assert_eq!( String::from("ADD COLUMN \"Array of Binary\" BYTEA[] NOT NULL"), sql ); } // #[test] // fn array_custom() { // let sql = Pg::add_column(true, "Array of Point", &types::array(&types::custom("POINT"))); // assert_eq!( // String::from("ADD COLUMN \"Array of Point\" POINT[] NOT NULL"), // sql // ); // } #[test] fn array_array_integer() { let sql = Pg::add_column( true, None, "Array of Array of Integer", &types::array(&types::array(&types::integer())), ); assert_eq!( String::from("ADD COLUMN \"Array of Array of Integer\" INTEGER[][] NOT NULL"), sql ); } barrel-0.7.0/src/tests/pg/create_table.rs000064400000000000000000000112160000000000000163740ustar 00000000000000//! Some unit tests that create create tables #![allow(unused_imports)] use crate::backend::{Pg, SqlGenerator}; use crate::{types, Migration, Table}; #[test] fn simple_table() { let mut m = Migration::new(); m.create_table("users", |_: &mut Table| {}); assert_eq!(m.make::(), String::from("CREATE TABLE \"users\" ();")); } #[test] fn create_table_if_not_exists_doesnt_hit_unreachable() { let mut m = Migration::new(); m.create_table_if_not_exists("artist", |t| { t.add_column("id", types::primary()); t.add_column("name", types::text().nullable(true)); t.add_column("description", types::text().nullable(true)); t.add_column("pic", types::text().nullable(true)); t.add_column("mbid", types::text().nullable(true)); }); assert_eq!(m.make::(), String::from("CREATE TABLE IF NOT EXISTS \"artist\" (\"id\" SERIAL PRIMARY KEY NOT NULL, \"name\" TEXT, \"description\" TEXT, \"pic\" TEXT, \"mbid\" TEXT);")); } #[test] fn basic_fields() { let mut m = Migration::new(); m.create_table("users", |t: &mut Table| { t.add_column("id", types::primary()); t.add_column("name", types::varchar(255)); t.add_column("age", types::integer()); t.add_column("plushy_sharks_owned", types::boolean()); }); assert_eq!( m.make::(), String::from("CREATE TABLE \"users\" (\"id\" SERIAL PRIMARY KEY NOT NULL, \"name\" VARCHAR(255) NOT NULL, \"age\" INTEGER NOT NULL, \"plushy_sharks_owned\" BOOLEAN NOT NULL);") ); } // #[test] // fn basic_fields_with_defaults() { // let mut m = Migration::new(); // m.create_table("users", |t: &mut Table| { // t.add_column("name", types::varchar(255)); // t.add_column("age", types::integer()); // t.add_column("plushy_sharks_owned", types::boolean()); // nobody is allowed plushy sharks // }); // assert_eq!( // m.make::(), // String::from("CREATE TABLE \"users\" (\"id\" SERIAL PRIMARY KEY NOT NULL, \"name\" VARCHAR(255) DEFAULT 'Anonymous' NOT NULL, \"age\" INTEGER DEFAULT '100' NOT NULL, \"plushy_sharks_owned\" BOOLEAN DEFAULT 'f' NOT NULL);") // ); // } #[test] fn basic_fields_nullable() { let mut m = Migration::new(); m.create_table("users", |t: &mut Table| { t.add_column("id", types::primary()); t.add_column("name", types::varchar(255).nullable(true)); t.add_column("age", types::integer().nullable(true)); t.add_column("plushy_sharks_owned", types::boolean().nullable(true)); }); assert_eq!( m.make::(), String::from("CREATE TABLE \"users\" (\"id\" SERIAL PRIMARY KEY NOT NULL, \"name\" VARCHAR(255), \"age\" INTEGER, \"plushy_sharks_owned\" BOOLEAN);") ); } // #[test]// fn simple_foreign_fields() { // let mut m = Migration::new(); // m.create_table("users", |t: &mut Table| { // t.add_column("id", types::primary()); // t.add_column("posts", types::foreign("poststypes::")); // () // }); // assert_eq!( // m.make::(), // String::from("CREATE TABLE \"users\" (\"id\" SERIAL PRIMARY KEY NOT NULL, \"posts\" INTEGER REFERENCES posts NOT NULL);") // ); // } #[test] fn create_multiple_tables() { let mut m = Migration::new(); m.create_table("artist", |t| { t.add_column("id", types::primary()); t.add_column("name", types::text()); t.add_column("description", types::text()); t.add_column("pic", types::text()); t.add_column("mbid", types::text()); }); m.create_table("album", |t| { t.add_column("id", types::primary()); t.add_column("name", types::text()); t.add_column("pic", types::text()); t.add_column("mbid", types::text()); }); assert_eq!(m.make::(), String::from("CREATE TABLE \"artist\" (\"id\" SERIAL PRIMARY KEY NOT NULL, \"name\" TEXT NOT NULL, \"description\" TEXT NOT NULL, \"pic\" TEXT NOT NULL, \"mbid\" TEXT NOT NULL);CREATE TABLE \"album\" (\"id\" SERIAL PRIMARY KEY NOT NULL, \"name\" TEXT NOT NULL, \"pic\" TEXT NOT NULL, \"mbid\" TEXT NOT NULL);")); } #[test] fn drop_table() { let mut m = Migration::new(); m.drop_table("users"); assert_eq!(m.make::(), String::from("DROP TABLE \"users\";")); } #[test] fn drop_table_if_exists() { let mut m = Migration::new(); m.drop_table_if_exists("users"); assert_eq!( m.make::(), String::from("DROP TABLE IF EXISTS \"users\";") ); } #[test] fn rename_table() { let mut m = Migration::new(); m.rename_table("users", "cool_users"); assert_eq!( m.make::(), String::from("ALTER TABLE \"users\" RENAME TO \"cool_users\";") ); } barrel-0.7.0/src/tests/pg/mod.rs000064400000000000000000000001300000000000000145320ustar 00000000000000//! Test pgsql generation mod add_column; mod create_table; mod reference; mod simple; barrel-0.7.0/src/tests/pg/reference.rs000064400000000000000000000012510000000000000157160ustar 00000000000000#![allow(unused_imports)] use crate::backend::{Pg, SqlGenerator}; use crate::{types, Migration, Table}; #[test] fn in_schema() { let sql = Pg::add_column( false, Some("schema"), "author", &types::foreign("users", "id"), ); assert_eq!( sql, "\"author\" INTEGER REFERENCES \"schema\".\"users\"(id) NOT NULL" ); } #[test] fn ext_schema() { let sql = Pg::add_column( false, Some("schema"), "author", &types::foreign_schema("other_schema", "users", "id"), ); assert_eq!( sql, "\"author\" INTEGER REFERENCES \"other_schema\".\"users\"(id) NOT NULL" ); } barrel-0.7.0/src/tests/pg/simple.rs000064400000000000000000000033220000000000000152520ustar 00000000000000//! Other simple table/ column migrations #![allow(unused_imports)] use crate::backend::{Pg, SqlGenerator}; #[test] fn create_table() { let sql = Pg::create_table("table_to_create", None); assert_eq!(String::from("CREATE TABLE \"table_to_create\""), sql); } #[test] fn create_table_with_schema() { let sql = Pg::create_table("table_to_create", Some("my_schema")); assert_eq!( String::from("CREATE TABLE \"my_schema\".\"table_to_create\""), sql ); } #[test] fn create_table_if_not_exists() { let sql = Pg::create_table_if_not_exists("table_to_create", None); assert_eq!( String::from("CREATE TABLE IF NOT EXISTS \"table_to_create\""), sql ); } #[test] fn drop_table() { let sql = Pg::drop_table("table_to_drop", None); assert_eq!(String::from("DROP TABLE \"table_to_drop\""), sql); } #[test] fn drop_table_if_exists() { let sql = Pg::drop_table_if_exists("table_to_drop", None); assert_eq!(String::from("DROP TABLE IF EXISTS \"table_to_drop\""), sql); } #[test] fn rename_table() { let sql = Pg::rename_table("old_table", "new_table", None); assert_eq!( String::from("ALTER TABLE \"old_table\" RENAME TO \"new_table\""), sql ); } #[test] fn alter_table() { let sql = Pg::alter_table("table_to_alter", None); assert_eq!(String::from("ALTER TABLE \"table_to_alter\""), sql); } #[test] fn drop_column() { let sql = Pg::drop_column("column_to_drop"); assert_eq!(String::from("DROP COLUMN \"column_to_drop\""), sql); } #[test] fn rename_column() { let sql = Pg::rename_column("old_column", "new_column"); assert_eq!( String::from("RENAME COLUMN \"old_column\" TO \"new_column\""), sql ); } barrel-0.7.0/src/tests/sqlite3/create_table.rs000064400000000000000000000033460000000000000173570ustar 00000000000000//! Some unit tests that create create tables #![allow(unused_imports)] use crate::backend::{SqlGenerator, Sqlite}; use crate::{types, Migration, Table}; #[test] fn create_multiple_tables() { let mut m = Migration::new(); m.create_table("artist", |t| { t.add_column("id", types::primary()); t.add_column("name", types::text().nullable(true)); t.add_column("description", types::text().nullable(true)); t.add_column("pic", types::text().nullable(true)); t.add_column("mbid", types::text().nullable(true)); }); m.create_table("album", |t| { t.add_column("id", types::primary()); t.add_column("name", types::text().nullable(true)); t.add_column("pic", types::text().nullable(true)); t.add_column("mbid", types::text().nullable(true)); }); assert_eq!(m.make::(), String::from("CREATE TABLE \"artist\" (\"id\" INTEGER NOT NULL PRIMARY KEY, \"name\" TEXT, \"description\" TEXT, \"pic\" TEXT, \"mbid\" TEXT);CREATE TABLE \"album\" (\"id\" INTEGER NOT NULL PRIMARY KEY, \"name\" TEXT, \"pic\" TEXT, \"mbid\" TEXT);")); } #[test] fn create_table_if_not_exists_doesnt_hit_unreachable() { let mut m = Migration::new(); m.create_table_if_not_exists("artist", |t| { t.add_column("id", types::primary()); t.add_column("name", types::text().nullable(true)); t.add_column("description", types::text().nullable(true)); t.add_column("pic", types::text().nullable(true)); t.add_column("mbid", types::text().nullable(true)); }); assert_eq!(m.make::(), String::from("CREATE TABLE IF NOT EXISTS \"artist\" (\"id\" INTEGER NOT NULL PRIMARY KEY, \"name\" TEXT, \"description\" TEXT, \"pic\" TEXT, \"mbid\" TEXT);")); } barrel-0.7.0/src/tests/sqlite3/mod.rs000064400000000000000000000001250000000000000155140ustar 00000000000000//! A few simple tests for the sqlite3 string backend mod create_table; mod simple; barrel-0.7.0/src/tests/sqlite3/simple.rs000064400000000000000000000026270000000000000162370ustar 00000000000000//! Other simple table/ column migrations #![allow(unused_imports)] use crate::backend::{SqlGenerator, Sqlite}; #[test] fn create_table() { let sql = Sqlite::create_table("table_to_create", None); assert_eq!(String::from("CREATE TABLE \"table_to_create\""), sql); } #[test] fn create_table_with_schema() { let sql = Sqlite::create_table("table_to_create", Some("my_schema")); assert_eq!( String::from("CREATE TABLE \"my_schema\".\"table_to_create\""), sql ); } #[test] fn create_table_if_not_exists() { let sql = Sqlite::create_table_if_not_exists("table_to_create", None); assert_eq!( String::from("CREATE TABLE IF NOT EXISTS \"table_to_create\""), sql ); } #[test] fn drop_table() { let sql = Sqlite::drop_table("table_to_drop", None); assert_eq!(String::from("DROP TABLE \"table_to_drop\""), sql); } #[test] fn drop_table_if_exists() { let sql = Sqlite::drop_table_if_exists("table_to_drop", None); assert_eq!(String::from("DROP TABLE IF EXISTS \"table_to_drop\""), sql); } #[test] fn rename_table() { let sql = Sqlite::rename_table("old_table", "new_table", None); assert_eq!( String::from("ALTER TABLE \"old_table\" RENAME TO \"new_table\""), sql ); } #[test] fn alter_table() { let sql = Sqlite::alter_table("table_to_alter", None); assert_eq!(String::from("ALTER TABLE \"table_to_alter\""), sql); } barrel-0.7.0/src/types/builders.rs000064400000000000000000000076540000000000000152020ustar 00000000000000//! Builder API's module use super::impls::Constraint; use super::impls::{BaseType, ReferentialAction, WrapVec}; use crate::types::Type; /// A standard primary numeric key type /// /// It's 64-bit wide, can't be null or non-unique /// and auto-increments on inserts. /// Maps to `primary` on `Pg` and manually enforces /// this behaviour for other Sql variants. pub fn primary() -> Type { Type::new(BaseType::Primary) .nullable(true) // Primary keys are non-null implicitly .increments(true) // This is ignored for now .primary(false) // Primary keys are primary implictly .unique(false) // Primary keys are unique implicitly .indexed(false) } /// A (standardised) UUID primary key type /// /// Similar to `primary()`, but uses a standard /// layout UUID type, mapping to `uuid` on `Pg` /// and not supported by all Sql variants. pub fn uuid() -> Type { Type::new(BaseType::UUID) .nullable(false) .unique(true) .indexed(true) } /// Create a basic integer type pub fn integer() -> Type { Type::new(BaseType::Integer) } /// Create an auto-incrementing integer type pub fn serial() -> Type { Type::new(BaseType::Serial) } /// A 32-bit floating point type pub fn float() -> Type { Type::new(BaseType::Float) } /// A 64-bit floating point type pub fn double() -> Type { Type::new(BaseType::Double) } /// A boolean data type (true, false) pub fn boolean() -> Type { Type::new(BaseType::Boolean) } /// A variable-length string type pub fn varchar(len: usize) -> Type { Type::new(BaseType::Varchar(len)) } /// A fixed-length string type pub fn r#char(len: usize) -> Type { Type::new(BaseType::Char(len)) } /// A variable-length string type pub fn text() -> Type { Type::new(BaseType::Text) } /// A json-type column – not supported by all backends pub fn json() -> Type { Type::new(BaseType::Json) } /// Embed binary data pub fn binary<'inner>() -> Type { Type::new(BaseType::Binary) } /// Create a column that points to some foreign table pub fn foreign( table: S, keys: I, on_update: ReferentialAction, on_delete: ReferentialAction, ) -> Type where S: Into, I: Into>, { Type::new(BaseType::Foreign( None, table.into(), keys.into(), on_update, on_delete, )) } /// Like `foreign(...)` but letting you provide an external schema /// /// This function is important when making cross-schema references pub fn foreign_schema( schema: S, table: S, keys: I, on_update: ReferentialAction, on_delete: ReferentialAction, ) -> Type where S: Into, I: Into>, { Type::new(BaseType::Foreign( Some(schema.into()), table.into(), keys.into(), on_update, on_delete, )) } /// Any custom SQL type that is embedded into a migration pub fn custom(sql: &'static str) -> Type { Type::new(BaseType::Custom(sql)) } /// An SQL date type pub fn date() -> Type { Type::new(BaseType::Date) } /// An SQL time type pub fn time() -> Type { Type::new(BaseType::Time) } /// An SQL datetime type pub fn datetime() -> Type { Type::new(BaseType::DateTime) } /// Create an array of inner types pub fn array(inner: &Type) -> Type { Type::new(BaseType::Array(Box::new(inner.get_inner()))) } /// Create an index over multiple, existing columns of the same type pub fn index(columns: I) -> Type where S: ToString, I: IntoIterator, { let vec: Vec = columns.into_iter().map(|s| s.to_string()).collect(); Type::new(BaseType::Index(vec)) } /// Create a constraint over multiple, existing columns of the same type pub fn unique_constraint(columns: I) -> Type where S: ToString, I: IntoIterator, { let vec: Vec = columns.into_iter().map(|s| s.to_string()).collect(); Type::new(BaseType::Constraint(Constraint::Unique, vec)) } barrel-0.7.0/src/types/defaults.rs000064400000000000000000000054570000000000000151770ustar 00000000000000use std::fmt::{self, Display, Formatter}; use std::time::SystemTime; use super::Type; use crate::functions::AutogenFunction; #[derive(PartialEq, Debug, Clone)] pub enum WrappedDefault<'outer> { /// Any text information AnyText(&'outer str), /// Simple integer Integer(i64), /// Floating point number Float(f32), /// Like Float but `~ ~ d o u b l e p r e c i s i o n ~ ~` Double(f64), /// A unique identifier type UUID(String), // TODO: Change to UUID type /// True or False Boolean(bool), /// Date And Time Date(SystemTime), /// Binary(&'outer [u8]), /// Foreign key to other table Foreign(Box), // I have no idea what you are – but I *like* it Custom(&'static str), /// Any of the above, but **many** of them Array(Vec), /// A function to call Function(AutogenFunction), /// Nothing Null, } impl<'outer> Display for WrappedDefault<'outer> { fn fmt(&self, f: &mut Formatter) -> fmt::Result { use self::WrappedDefault::*; write!( f, "{}", &match *self { AnyText(ref val) => format!("{}", val), Integer(ref val) => format!("{}", val), Float(ref val) => format!("{}", val), Double(ref val) => format!("{}", val), UUID(ref val) => format!("{}", val), Boolean(ref val) => format!("{}", val), Date(ref val) => format!("{:?}", val), Binary(ref val) => format!("{:?}", val), Foreign(ref val) => format!("{:?}", val), Custom(ref val) => format!("{}", val), Array(ref val) => format!("{:?}", val), Function(ref fun) => format!("{:?}", fun), Null => format!("NULL"), } ) } } impl From for WrappedDefault<'static> { fn from(fun: AutogenFunction) -> Self { WrappedDefault::Function(fun) } } impl From<&'static str> for WrappedDefault<'static> { fn from(s: &'static str) -> Self { WrappedDefault::AnyText(s) } } impl From for WrappedDefault<'static> { fn from(s: i64) -> Self { WrappedDefault::Integer(s) } } impl From for WrappedDefault<'static> { fn from(s: f32) -> Self { WrappedDefault::Float(s) } } impl From for WrappedDefault<'static> { fn from(s: f64) -> Self { WrappedDefault::Double(s) } } impl From for WrappedDefault<'static> { fn from(s: bool) -> Self { WrappedDefault::Boolean(s) } } impl From for WrappedDefault<'static> { fn from(s: SystemTime) -> Self { WrappedDefault::Date(s) } } pub fn null() -> WrappedDefault<'static> { WrappedDefault::Null } barrel-0.7.0/src/types/impls.rs000064400000000000000000000141440000000000000145050ustar 00000000000000//! Implementation specifics for the type system use std::fmt; use super::WrappedDefault; /// A smol wrapper around `Vec` to get around the orphan rules #[derive(PartialEq, Debug, Clone)] pub struct WrapVec(pub Vec); #[derive(PartialEq, Debug, Clone)] pub enum Constraint { Unique, } impl fmt::Display for Constraint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Unique => write!(f, "UNIQUE"), } } } /// Core type enum, describing the basic type #[derive(PartialEq, Debug, Clone)] pub enum BaseType { /// A string blob, stored in the heap with a pointer in the row Text, /// Variable-length string that (hopefully) is stored to the row Varchar(usize), /// Fixed-length string that is stored to the row Char(usize), /// Primary key (utility for incrementing integer – postgres supports this, we just mirror it) Primary, /// Simple integer Integer, /// An integer that as a default value of the next biggest number Serial, /// Floating point number Float, /// Like Float but `~ ~ d o u b l e p r e c i s i o n ~ ~` Double, /// A unique identifier type UUID, /// True or False Boolean, /// Json encoded data Json, /// Date Date, /// Date Time, /// Date and time DateTime, /// Binary, /// Foreign key to other table Foreign( Option, String, WrapVec, ReferentialAction, ReferentialAction, ), /// I have no idea what you are – but I *like* it Custom(&'static str), /// Any of the above, but **many** of them Array(Box), /// Indexing over multiple columns Index(Vec), /// Indexing over multiple columns Constraint(Constraint, Vec), } /// A database column type and all the metadata attached to it /// /// Using this struct directly is not recommended. Instead, you should be /// using the constructor APIs in the `types` module. /// /// A `Type` is an enum provided to other `barrel` APIs in order /// to generate SQL datatypes. Working with them directly is possible /// but not recommended. /// /// Instead, you can use these helper functions to construct `Type` enums of /// different...types and constraints. Field metadata is added via chainable /// factory pattern functions. /// /// ## Default values /// /// If no additional arguments are provided, some assumptions will be made /// about the metadata of a column type. /// /// - `nullable`: `false` /// - `indexed`: `false` /// - `unique`: `false` /// - `default`: `None` /// - `size`: `None` (which will error if size is important) /// /// ## Examples /// /// ```rust,no_run /// extern crate barrel; /// use barrel::types::*; /// /// // Make your own Primary key :) /// let col = integer().increments(true).unique(true); /// ``` #[derive(Debug, Clone, PartialEq)] pub struct Type { pub nullable: bool, pub unique: bool, pub increments: bool, pub indexed: bool, pub primary: bool, pub default: Option>, pub size: Option, pub inner: BaseType, } /// This is a public API, be considered about breaking thigns #[cfg_attr(rustfmt, rustfmt_skip)] impl Type { pub(crate) fn new(inner: BaseType) -> Self { Self { nullable: false, unique: false, increments: false, indexed: false, primary: false, default: None, size: None, inner, } } /// Function used to hide the inner type to outside users (sneaky, I know) pub(crate) fn get_inner(&self) -> BaseType { self.inner.clone() } /// Set the nullability of this type pub fn nullable(self, arg: bool) -> Self { Self { nullable: arg, ..self } } /// Set the uniqueness of this type pub fn unique(self, arg: bool) -> Self { Self { unique: arg, ..self } } /// Specify if this type should auto-increment pub fn increments(self, arg: bool) -> Self { Self { increments: arg, ..self } } /// Specify if this type should be indexed by your SQL implementation pub fn indexed(self, arg: bool) -> Self { Self { indexed: arg, ..self } } /// Specify if this type should be a primary key pub fn primary(self, arg: bool) -> Self { Self { primary: arg, ..self } } /// Provide a default value for a type column pub fn default(self, arg: impl Into>) -> Self { Self { default: Some(arg.into()), ..self } } /// Specify a size limit (important or varchar & similar) pub fn size(self, arg: usize) -> Self { Self { size: Some(arg), ..self } } } /// Describes the referential_action for ON DELETE and ON UPDATE statements. #[derive(Clone, Debug, PartialEq, Eq)] pub enum ReferentialAction { Unset, NoAction, Restrict, SetNull, SetDefault, Cascade, } impl ReferentialAction { /// Returns the ON DELETE statement pub fn on_delete(&self) -> String { format!("ON DELETE {}", self) } /// Returns the ON UPDATE statement pub fn on_update(&self) -> String { format!("ON UPDATE {}", self) } } impl std::fmt::Display for ReferentialAction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let s = match self { Self::Unset => panic!("The Unset variant cannot be displayed!"), Self::NoAction => "NO ACTION", Self::Restrict => "RESTRICT", Self::SetNull => "SET NULL", Self::SetDefault => "SET DEFAULT", Self::Cascade => "CASCADE", }; write!(f, "{}", s) } } impl<'a> From<&'a str> for WrapVec { fn from(s: &'a str) -> Self { WrapVec(vec![s.into()]) } } impl From for WrapVec { fn from(s: String) -> Self { WrapVec(vec![s]) } } impl From> for WrapVec where I: Into, { fn from(v: Vec) -> Self { WrapVec(v.into_iter().map(|s| s.into()).collect()) } } barrel-0.7.0/src/types/mod.rs000064400000000000000000000003240000000000000141330ustar 00000000000000//! Types constructor module mod builders; mod defaults; mod impls; pub use self::builders::*; pub use self::defaults::{null, WrappedDefault}; pub use self::impls::{BaseType, ReferentialAction, Type, WrapVec};