diff --git a/development/libs/barrel/.envrc b/development/libs/barrel/.envrc new file mode 100644 index 00000000000..051d09d292a --- /dev/null +++ b/development/libs/barrel/.envrc @@ -0,0 +1 @@ +eval "$(lorri direnv)" diff --git a/development/libs/barrel/.gitignore b/development/libs/barrel/.gitignore new file mode 100644 index 00000000000..143b1ca0147 --- /dev/null +++ b/development/libs/barrel/.gitignore @@ -0,0 +1,4 @@ + +/target/ +**/*.rs.bk +Cargo.lock diff --git a/development/libs/barrel/.travis.yml b/development/libs/barrel/.travis.yml new file mode 100644 index 00000000000..df8007fe728 --- /dev/null +++ b/development/libs/barrel/.travis.yml @@ -0,0 +1,48 @@ +# 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 diff --git a/development/libs/barrel/Cargo.toml b/development/libs/barrel/Cargo.toml new file mode 100644 index 00000000000..3662add903d --- /dev/null +++ b/development/libs/barrel/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "barrel" +version = "0.6.6-alpha.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 = [] +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 } diff --git a/development/libs/barrel/LICENSE b/development/libs/barrel/LICENSE new file mode 100644 index 00000000000..63883845baa --- /dev/null +++ b/development/libs/barrel/LICENSE @@ -0,0 +1,21 @@ +MIT 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. diff --git a/development/libs/barrel/README.md b/development/libs/barrel/README.md new file mode 100644 index 00000000000..46f769ce2e3 --- /dev/null +++ b/development/libs/barrel/README.md @@ -0,0 +1,78 @@ +![](assets/logo.svg) + +[![](https://travis-ci.org/rust-db/barrel.svg?branch=master)](https://travis-ci.org/rust-db/barrel) +[![](https://ci.appveyor.com/api/projects/status/7e00r2e1xatxk3bj?svg=true)](https://ci.appveyor.com/project/spacekookie/barrel) +[![](https://coveralls.io/repos/github/rust-db/barrel/badge.svg?branch=master&service=github)](https://coveralls.io/github/rust-db/barrel?branch=master) +[![](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). diff --git a/development/libs/barrel/appveyor.yml b/development/libs/barrel/appveyor.yml new file mode 100644 index 00000000000..02398d09eb7 --- /dev/null +++ b/development/libs/barrel/appveyor.yml @@ -0,0 +1,14 @@ +build: 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 + diff --git a/development/libs/barrel/assets/logo.svg b/development/libs/barrel/assets/logo.svg new file mode 100644 index 00000000000..e98960f43e1 --- /dev/null +++ b/development/libs/barrel/assets/logo.svg @@ -0,0 +1,173 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + SQL + + + + + diff --git a/development/libs/barrel/barrel_derives/.gitignore b/development/libs/barrel/barrel_derives/.gitignore new file mode 100644 index 00000000000..143b1ca0147 --- /dev/null +++ b/development/libs/barrel/barrel_derives/.gitignore @@ -0,0 +1,4 @@ + +/target/ +**/*.rs.bk +Cargo.lock diff --git a/development/libs/barrel/barrel_derives/Cargo.toml b/development/libs/barrel/barrel_derives/Cargo.toml new file mode 100644 index 00000000000..ba5f9aa7280 --- /dev/null +++ b/development/libs/barrel/barrel_derives/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "barrel_derives" +version = "0.0.0" +authors = ["Katharina Fey "] + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "^0.12", features = ["extra-traits"] } +quote = "^0.4" \ No newline at end of file diff --git a/development/libs/barrel/barrel_derives/src/lib.rs b/development/libs/barrel/barrel_derives/src/lib.rs new file mode 100644 index 00000000000..e66e93bbe75 --- /dev/null +++ b/development/libs/barrel/barrel_derives/src/lib.rs @@ -0,0 +1,81 @@ +//! A crate that handles some of the finer magical points of barrel error handling +//! +//! The idea behind this is that most programmer errors will be caught when +//! compiling the migrations, instead of during the runtime of a migration. +//! +//! This means less hassle on your production system and better testability πŸŽ‰ +//! +//! More docs will follow here as the module matures + + +extern crate proc_macro; +extern crate syn; + +#[macro_use] +extern crate quote; + +use proc_macro::TokenStream; +use syn::DeriveInput; + +#[proc_macro_derive(TextType)] +pub fn typed(input: TokenStream) -> TokenStream { + + // Parse the input tokens into a syntax tree + let ast: DeriveInput = syn::parse(input).unwrap(); + let name = &ast.ident; + + panic!("name: {}", name); + + // Build the output, possibly using quasi-quotation + let expanded = quote! { + + }; + + // Hand the output tokens back to the compiler + expanded.into() +} + +// extern crate proc_macro; +// extern crate syn; + +// #[macro_use] +// extern crate quote; + +// use proc_macro::TokenStream; + +// #[proc_macro_derive(Typed)] +// pub fn column_type_check(input: TokenStream) -> TokenStream { +// let s = input.to_string(); +// let ast = syn::parse(input).unwrap(); + +// panic!(s); + +// // unimplemented!(); +// } + + +// #[proc_macro_derive(HelloWorld)] +// pub fn hello_world(input: TokenStream) -> TokenStream { +// // Construct a string representation of the type definition +// let s = input.to_string(); + +// // Parse the string representation +// let ast = syn::parse_derive_input(&s).unwrap(); + +// // Build the impl +// let gen = impl_hello_world(&ast); + +// // Return the generated impl +// gen.parse().unwrap() +// } + +// fn impl_hello_world(ast: &syn::DeriveInput) -> quote::Tokens { +// let name = &ast.ident; +// quote! { +// impl HelloWorld for #name { +// fn hello_world() { +// println!("Hello, World! My name is {}", stringify!(#name)); +// } +// } +// } +// } \ No newline at end of file diff --git a/development/libs/barrel/code_of_conduct.md b/development/libs/barrel/code_of_conduct.md new file mode 100644 index 00000000000..79c15bbd9dc --- /dev/null +++ b/development/libs/barrel/code_of_conduct.md @@ -0,0 +1,76 @@ +# 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/ diff --git a/development/libs/barrel/docs/.gitignore b/development/libs/barrel/docs/.gitignore new file mode 100644 index 00000000000..8ee89848fc9 --- /dev/null +++ b/development/libs/barrel/docs/.gitignore @@ -0,0 +1,4 @@ +output +**/*.pyc +env +*.pid diff --git a/development/libs/barrel/docs/LICENSE b/development/libs/barrel/docs/LICENSE new file mode 100644 index 00000000000..f349240fec5 --- /dev/null +++ b/development/libs/barrel/docs/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2018 Katharina + +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. + diff --git a/development/libs/barrel/docs/Makefile b/development/libs/barrel/docs/Makefile new file mode 100755 index 00000000000..4579789ae71 --- /dev/null +++ b/development/libs/barrel/docs/Makefile @@ -0,0 +1,124 @@ +PY?=python3 +PELICAN?=pelican +PELICANOPTS= + +BASEDIR=$(CURDIR) +INPUTDIR=$(BASEDIR)/content +OUTPUTDIR=$(BASEDIR)/output +CONFFILE=$(BASEDIR)/pelicanconf.py +PUBLISHCONF=$(BASEDIR)/publishconf.py + +FTP_HOST=localhost +FTP_USER=anonymous +FTP_TARGET_DIR=/ + +SSH_HOST=localhost +SSH_PORT=22 +SSH_USER=root +SSH_TARGET_DIR=/var/www + +S3_BUCKET=my_s3_bucket + +CLOUDFILES_USERNAME=my_rackspace_username +CLOUDFILES_API_KEY=my_rackspace_api_key +CLOUDFILES_CONTAINER=my_cloudfiles_container + +DROPBOX_DIR=~/Dropbox/Public/ + +GITHUB_PAGES_BRANCH=gh-pages + +DEBUG ?= 0 +ifeq ($(DEBUG), 1) + PELICANOPTS += -D +endif + +RELATIVE ?= 0 +ifeq ($(RELATIVE), 1) + PELICANOPTS += --relative-urls +endif + +help: + @echo 'Makefile for a pelican Web site ' + @echo ' ' + @echo 'Usage: ' + @echo ' make html (re)generate the web site ' + @echo ' make clean remove the generated files ' + @echo ' make regenerate regenerate files upon modification ' + @echo ' make publish generate using production settings ' + @echo ' make serve [PORT=8000] serve site at http://localhost:8000' + @echo ' make serve-global [SERVER=0.0.0.0] serve (as root) to $(SERVER):80 ' + @echo ' make devserver [PORT=8000] start/restart develop_server.sh ' + @echo ' make stopserver stop local server ' + @echo ' make ssh_upload upload the web site via SSH ' + @echo ' make rsync_upload upload the web site via rsync+ssh ' + @echo ' make dropbox_upload upload the web site via Dropbox ' + @echo ' make ftp_upload upload the web site via FTP ' + @echo ' make s3_upload upload the web site via S3 ' + @echo ' make cf_upload upload the web site via Cloud Files' + @echo ' make github upload the web site via gh-pages ' + @echo ' ' + @echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html ' + @echo 'Set the RELATIVE variable to 1 to enable relative urls ' + @echo ' ' + +html: + $(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) + +clean: + [ ! -d $(OUTPUTDIR) ] || rm -rf $(OUTPUTDIR) + +regenerate: + $(PELICAN) -r $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) + +serve: +ifdef PORT + cd $(OUTPUTDIR) && $(PY) -m pelican.server $(PORT) +else + cd $(OUTPUTDIR) && $(PY) -m pelican.server +endif + +serve-global: +ifdef SERVER + cd $(OUTPUTDIR) && $(PY) -m pelican.server 80 $(SERVER) +else + cd $(OUTPUTDIR) && $(PY) -m pelican.server 80 0.0.0.0 +endif + + +devserver: +ifdef PORT + $(BASEDIR)/develop_server.sh restart $(PORT) > /dev/null +else + $(BASEDIR)/develop_server.sh restart > /dev/null +endif + +stopserver: + $(BASEDIR)/develop_server.sh stop + @echo 'Stopped Pelican and SimpleHTTPServer processes running in background.' + +publish: + $(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(PUBLISHCONF) $(PELICANOPTS) + +ssh_upload: publish + scp -P $(SSH_PORT) -r $(OUTPUTDIR)/* $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR) + +rsync_upload: publish + rsync -e "ssh -p $(SSH_PORT)" -P -rvzc --delete $(OUTPUTDIR)/ $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR) --cvs-exclude + +dropbox_upload: publish + cp -r $(OUTPUTDIR)/* $(DROPBOX_DIR) + +ftp_upload: publish + lftp ftp://$(FTP_USER)@$(FTP_HOST) -e "mirror -R $(OUTPUTDIR) $(FTP_TARGET_DIR) ; quit" + +s3_upload: publish + s3cmd sync $(OUTPUTDIR)/ s3://$(S3_BUCKET) --acl-public --delete-removed --guess-mime-type + +cf_upload: publish + cd $(OUTPUTDIR) && swift -v -A https://auth.api.rackspacecloud.com/v1.0 -U $(CLOUDFILES_USERNAME) -K $(CLOUDFILES_API_KEY) upload -c $(CLOUDFILES_CONTAINER) . + +github: publish + ghp-import -m "Generate Pelican site" -b $(GITHUB_PAGES_BRANCH) $(OUTPUTDIR) + git push origin $(GITHUB_PAGES_BRANCH) + +.PHONY: html help clean regenerate serve serve-global devserver publish ssh_upload rsync_upload dropbox_upload ftp_upload s3_upload cf_upload github diff --git a/development/libs/barrel/docs/README.md b/development/libs/barrel/docs/README.md new file mode 100755 index 00000000000..987480b84aa --- /dev/null +++ b/development/libs/barrel/docs/README.md @@ -0,0 +1,3 @@ +# `barrel` website + + diff --git a/development/libs/barrel/docs/blue-penguin/CONTRIBUTORS.md b/development/libs/barrel/docs/blue-penguin/CONTRIBUTORS.md new file mode 100644 index 00000000000..f0e620bbd81 --- /dev/null +++ b/development/libs/barrel/docs/blue-penguin/CONTRIBUTORS.md @@ -0,0 +1,16 @@ +# Contributors + +* [Nevan Scott](https://github.com/nevanscott/Mockingbird) (original author) +* [wrl](http://ghttps://github.com/guikcdithub.com/wrl) (port to pelican, pelican-mockingbird) +* [Jody Frankowski](http://github.com/jody-frankowski) (Blue Penguin) +* [Grimbox](https://github.com/Grimbox) +* [ix5](https://github.com/ix5) +* [dn0](https://github.com/dn0) +* [anhtuann](https://github.com/anhtuann) +* [aperep](https://github.com/aperep) +* [iranzo](https://github.com/iranzo) +* [thetlk](https://github.com/thetlk) +* [SnorlaxYum](https://github.com/SnorlaxYum) +* [guikcd](https://github.com/guikcd) +* [jorgesumle](https://github.com/jorgesumle) +* [crxxn](https://github.com/crxxn) diff --git a/development/libs/barrel/docs/blue-penguin/README.md b/development/libs/barrel/docs/blue-penguin/README.md new file mode 100644 index 00000000000..831bfc3364d --- /dev/null +++ b/development/libs/barrel/docs/blue-penguin/README.md @@ -0,0 +1,53 @@ +![screenshot](screenshot.png) + +# Blue Penguin for pelican +A simple theme for pelican. Solarized pygments. Feeds support. + +## Settings +```python +# all the following settings are *optional* + +# HTML metadata +SITEDESCRIPTION = '' + +# all defaults to True. +DISPLAY_HEADER = True +DISPLAY_FOOTER = True +DISPLAY_HOME = True +DISPLAY_MENU = True + +# provided as examples, they make β€˜clean’ urls. used by MENU_INTERNAL_PAGES. +TAGS_URL = 'tags' +TAGS_SAVE_AS = 'tags/index.html' +AUTHORS_URL = 'authors' +AUTHORS_SAVE_AS = 'authors/index.html' +CATEGORIES_URL = 'categories' +CATEGORIES_SAVE_AS = 'categories/index.html' +ARCHIVES_URL = 'archives' +ARCHIVES_SAVE_AS = 'archives/index.html' + +# use those if you want pelican standard pages to appear in your menu +MENU_INTERNAL_PAGES = ( + ('Tags', TAGS_URL, TAGS_SAVE_AS), + ('Authors', AUTHORS_URL, AUTHORS_SAVE_AS), + ('Categories', CATEGORIES_URL, CATEGORIES_SAVE_AS), + ('Archives', ARCHIVES_URL, ARCHIVES_SAVE_AS), +) +# additional menu items +MENUITEMS = ( + ('GitHub', 'https://github.com/'), + ('Linux Kernel', 'https://www.kernel.org/'), +) +``` + +## How to contribute +Contributions are very welcome. Keep in mind that this theme goal is to be +minimalistic/simple. Contributions will be accepted through Github Pull +Requests. If you don’t have a Github account you can suggest me your +changes by email (which you can find on my github profile). + +## Contributors +See [CONTRIBUTORS.md](CONTRIBUTORS.md). + +## License +Public domain. diff --git a/development/libs/barrel/docs/blue-penguin/screenshot.png b/development/libs/barrel/docs/blue-penguin/screenshot.png new file mode 100644 index 00000000000..fc5c4062932 Binary files /dev/null and b/development/libs/barrel/docs/blue-penguin/screenshot.png differ diff --git a/development/libs/barrel/docs/blue-penguin/static/css/print.css b/development/libs/barrel/docs/blue-penguin/static/css/print.css new file mode 100644 index 00000000000..c63a87bff61 --- /dev/null +++ b/development/libs/barrel/docs/blue-penguin/static/css/print.css @@ -0,0 +1,4 @@ +* { background: #fff; } +body { font-family: georgia, times, serif; color: black; } +blockquote { font-style: italic; color: black; } +a:link, a:visited { border-bottom-width: 1px; border-bottom-style: solid; } diff --git a/development/libs/barrel/docs/blue-penguin/static/css/pygments.css b/development/libs/barrel/docs/blue-penguin/static/css/pygments.css new file mode 100644 index 00000000000..91ae32653fe --- /dev/null +++ b/development/libs/barrel/docs/blue-penguin/static/css/pygments.css @@ -0,0 +1,87 @@ +/* Solarized Dark + +For use with Jekyll and Pygments + +http://ethanschoonover.com/solarized + +SOLARIZED HEX ROLE +--------- -------- ------------------------------------------ +base03 #002b36 background +base01 #586e75 comments / secondary content +base1 #93a1a1 body text / default code / primary content +orange #cb4b16 constants +red #dc322f regex, special keywords +blue #268bd2 reserved keywords +cyan #2aa198 strings, numbers +green #859900 operators, other keywords +*/ + +.highlight { background-color: #002b36; color: #93a1a1 } +.highlight .c { color: #586e75 } /* Comment */ +.highlight .err { color: #93a1a1 } /* Error */ +.highlight .g { color: #93a1a1 } /* Generic */ +.highlight .k { color: #859900 } /* Keyword */ +.highlight .l { color: #93a1a1 } /* Literal */ +.highlight .n { color: #93a1a1 } /* Name */ +.highlight .o { color: #859900 } /* Operator */ +.highlight .x { color: #cb4b16 } /* Other */ +.highlight .p { color: #93a1a1 } /* Punctuation */ +.highlight .cm { color: #586e75 } /* Comment.Multiline */ +.highlight .cp { color: #859900 } /* Comment.Preproc */ +.highlight .c1 { color: #586e75 } /* Comment.Single */ +.highlight .cs { color: #859900 } /* Comment.Special */ +.highlight .gd { color: #2aa198 } /* Generic.Deleted */ +.highlight .ge { color: #93a1a1; font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #dc322f } /* Generic.Error */ +.highlight .gh { color: #cb4b16 } /* Generic.Heading */ +.highlight .gi { color: #859900 } /* Generic.Inserted */ +.highlight .go { color: #93a1a1 } /* Generic.Output */ +.highlight .gp { color: #93a1a1 } /* Generic.Prompt */ +.highlight .gs { color: #93a1a1; font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #cb4b16 } /* Generic.Subheading */ +.highlight .gt { color: #93a1a1 } /* Generic.Traceback */ +.highlight .kc { color: #cb4b16 } /* Keyword.Constant */ +.highlight .kd { color: #268bd2 } /* Keyword.Declaration */ +.highlight .kn { color: #859900 } /* Keyword.Namespace */ +.highlight .kp { color: #859900 } /* Keyword.Pseudo */ +.highlight .kr { color: #268bd2 } /* Keyword.Reserved */ +.highlight .kt { color: #dc322f } /* Keyword.Type */ +.highlight .ld { color: #93a1a1 } /* Literal.Date */ +.highlight .m { color: #2aa198 } /* Literal.Number */ +.highlight .s { color: #2aa198 } /* Literal.String */ +.highlight .na { color: #93a1a1 } /* Name.Attribute */ +.highlight .nb { color: #B58900 } /* Name.Builtin */ +.highlight .nc { color: #268bd2 } /* Name.Class */ +.highlight .no { color: #cb4b16 } /* Name.Constant */ +.highlight .nd { color: #268bd2 } /* Name.Decorator */ +.highlight .ni { color: #cb4b16 } /* Name.Entity */ +.highlight .ne { color: #cb4b16 } /* Name.Exception */ +.highlight .nf { color: #268bd2 } /* Name.Function */ +.highlight .nl { color: #93a1a1 } /* Name.Label */ +.highlight .nn { color: #93a1a1 } /* Name.Namespace */ +.highlight .nx { color: #93a1a1 } /* Name.Other */ +.highlight .py { color: #93a1a1 } /* Name.Property */ +.highlight .nt { color: #268bd2 } /* Name.Tag */ +.highlight .nv { color: #268bd2 } /* Name.Variable */ +.highlight .ow { color: #859900 } /* Operator.Word */ +.highlight .w { color: #93a1a1 } /* Text.Whitespace */ +.highlight .mf { color: #2aa198 } /* Literal.Number.Float */ +.highlight .mh { color: #2aa198 } /* Literal.Number.Hex */ +.highlight .mi { color: #2aa198 } /* Literal.Number.Integer */ +.highlight .mo { color: #2aa198 } /* Literal.Number.Oct */ +.highlight .sb { color: #586e75 } /* Literal.String.Backtick */ +.highlight .sc { color: #2aa198 } /* Literal.String.Char */ +.highlight .sd { color: #93a1a1 } /* Literal.String.Doc */ +.highlight .s2 { color: #2aa198 } /* Literal.String.Double */ +.highlight .se { color: #cb4b16 } /* Literal.String.Escape */ +.highlight .sh { color: #93a1a1 } /* Literal.String.Heredoc */ +.highlight .si { color: #2aa198 } /* Literal.String.Interpol */ +.highlight .sx { color: #2aa198 } /* Literal.String.Other */ +.highlight .sr { color: #dc322f } /* Literal.String.Regex */ +.highlight .s1 { color: #2aa198 } /* Literal.String.Single */ +.highlight .ss { color: #2aa198 } /* Literal.String.Symbol */ +.highlight .bp { color: #268bd2 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #268bd2 } /* Name.Variable.Class */ +.highlight .vg { color: #268bd2 } /* Name.Variable.Global */ +.highlight .vi { color: #268bd2 } /* Name.Variable.Instance */ +.highlight .il { color: #2aa198 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/development/libs/barrel/docs/blue-penguin/static/css/screen.css b/development/libs/barrel/docs/blue-penguin/static/css/screen.css new file mode 100644 index 00000000000..dc11acf47cf --- /dev/null +++ b/development/libs/barrel/docs/blue-penguin/static/css/screen.css @@ -0,0 +1,342 @@ +/* http://meyerweb.com/eric/tools/css/reset/ +v2.0 | 20110126 +License: none (public domain) + */ +/* Mockingbird Theme by Nevan Scott nevanscott.com */ +/* Modified by Jody Frankowski */ +/* Modified by ix5 */ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +em { + font-style: italic; +} +strong { + font-weight: bold; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} + +body { + font-family: Georgia, serif; + font-size: 16px; + line-height: 1.5em; + color: #444; +} + +header, #wrapper { + padding: 0 10px; + min-width: 500px; + max-width: 910px; + margin: auto; +} + +a { + text-decoration: none; + color: #15A9DB; +} + +ul { + list-style: outside disc; +} + +ol { + list-style: outside decimal; +} + +h1, h2, h3, h4, h5, h6 { + font-family: sans-serif; + font-weight: bold; +} +h1, h2, h3 { + font-size: 1.5em; + line-height: 1em; + margin: 1em 0; +} + +img, p, .post > .highlight, .highlighttable, h4, h5, h6 { + margin-top: 1.2em; +} + +blockquote { + margin: 1.5em 1.5em 1.5em .75em; + padding-left: .75em; + border-left: 1px solid #EEE; +} + +.date { + color: #CCC; + float: left; + clear: both; + width: 130px; + font-size: 1.5em; + line-height: 1em; + margin: 0 20px 1em 0; +} + +.info { + margin-top: 1.3em; + font-family: sans-serif; + text-align: right; + color: #BBB; +} +.info a { + color: inherit; +} +.info a.tags { + background: #CCC; + color: #FFF; + display: inline-block; + padding: 0 .3em; + border: 1px transparent solid; + border-radius: 5px; + margin: 0 0 0.3em 0; +} +.info a.tags:hover { + background: inherit; + color: inherit; +} +.info a.tags.selected { + border: 1px #999 solid; +} + +.post { + margin: 0 0 4.5em 150px; +} +.post.archives { + margin-bottom: 1.5em; + margin-left: 160px; +} +.post p { + text-align: justify; +} + +.page { + margin: 0 90px; +} + +.highlight { + border-radius: 3px; +} +.code > .highlight { + border-radius: 0px 3px 3px 0px; +} +.linenos { + border-radius: 3px 0px 0px 3px; + background-color: #073642; + border-right: 1px solid #00232C; + color: #586E75; + text-shadow: 0px -1px #021014; +} +td.code { + width: 100%; + max-width: 100px; +} +.linenos a { + color: #586E75; +} + +img { + box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.15); + border-radius: 0.3em; + max-width: 100%; + display: block; + margin-left: auto; + margin-right: auto; +} + +/*sub and sup stolen from Twitter bootstrap.*/ +sub, sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +.post pre, .page pre { + padding: .8em; + font-size: 12px; + font-family: Monospace; + line-height: 1.1em; + overflow: auto; +} + +form.inline_edit { + clear: both; + margin: 4.5em 0; + background-color: #DDD; + color: #000; + padding: 20px; + border-radius: 5px; +} +.inline_edit .sub { + color: #888; + white-space: nowrap; +} +.inline_edit label { + float: left; + clear: both; + width: 140px; + margin-right: 20px; +} +.inline_edit .buttons { + display: block; + text-align: right; +} + +nav ul { + float: right; + list-style: none; + margin: 0 0 0 3em; + padding: 0; +} +nav li { + float: left; +} +nav a { + display: block; + padding: 4.5em 10px 10px 10px; +} +nav a:hover { + background-color: #d3d3d3; + color: #FFF; +} +nav li.selected a { + background-color: #15A9DB; + color: #FFF; +} + +header .header_box { + padding-top: 4.5em; +} +header h1 { + font-size: 1.5em; + line-height: 1em; + margin: 0; +} +header h2 { + font-size: 1em; + margin: .3em 0; + color: #DDD; +} + +#content { + margin-top: 3em; +} + +.pages { + font-family: sans-serif; + line-height: 2.5em; + margin: 4.5em 0 3em; + background-color: #F9F9F9; + color: #444; + border-radius: 5px; +} +.pages a.next_page { + float: right; + width: 140px; + text-align: center; + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; + background-color: #EEE; +} +.pages a.prev_page { + float: left; + width: 140px; + text-align: center; + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; + background-color: #EEE; +} +.pages a { + color: inherit; + border: none; +} +.pages a:hover { + background-color: #DDD; +} +.pages span { + display: block; + margin: 0 160px; + text-align: center; +} + +code { + background-color: #F9F2F4; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + box-sizing: border-box; + color: #C7254E; + font-family: Monaco,Menlo,Consolas,"Courier New",monospace; + font-size: 12.6px; + line-height: 18px; + padding-bottom: 2px; + padding-left: 4px; + padding-right: 4px; + padding-top: 2px; + white-space: nowrap; +} + +footer { + font-family: sans-serif; + line-height: 2.5em; + text-align: center; + color: #BBB; + margin: 3em 0; + border: 1px solid #EEE; + border-radius: 5px; +} +footer p { margin: 0; } + +.right { float: right; } + +.clear { clear: both; } diff --git a/development/libs/barrel/docs/blue-penguin/templates/analytics.html b/development/libs/barrel/docs/blue-penguin/templates/analytics.html new file mode 100644 index 00000000000..ba174fcc20a --- /dev/null +++ b/development/libs/barrel/docs/blue-penguin/templates/analytics.html @@ -0,0 +1,11 @@ +{% if GOOGLE_ANALYTICS %} + + +{% endif %} \ No newline at end of file diff --git a/development/libs/barrel/docs/blue-penguin/templates/archives.html b/development/libs/barrel/docs/blue-penguin/templates/archives.html new file mode 100644 index 00000000000..73c53bae8b0 --- /dev/null +++ b/development/libs/barrel/docs/blue-penguin/templates/archives.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} + +{% block title %}{{ SITENAME }} | Archives{% endblock %} +{% block content %} + +

Archives

+ + {# based on http://stackoverflow.com/questions/12764291/jinja2-group-by-month-year #} + + {% for year, year_group in dates|groupby('date.year')|reverse %} + {% for month, month_group in year_group|groupby('date.month')|reverse %} +

{{ (month_group|first).date|strftime('%b %Y') }}

+
+ +
+ {% endfor %} + {% endfor %} +{% endblock %} diff --git a/development/libs/barrel/docs/blue-penguin/templates/article.html b/development/libs/barrel/docs/blue-penguin/templates/article.html new file mode 100644 index 00000000000..d8e7071e9eb --- /dev/null +++ b/development/libs/barrel/docs/blue-penguin/templates/article.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block head %} + {{ super() }} + {% if article.tags %} + + {% endif %} + {% if article.description %} + + {% endif %} +{% endblock %} + +{% block title %}{{ SITENAME }} | {{ article.title }}{% endblock %} + +{% block content %} +{% include "article_stub.html" %} +{% endblock %} diff --git a/development/libs/barrel/docs/blue-penguin/templates/article_stub.html b/development/libs/barrel/docs/blue-penguin/templates/article_stub.html new file mode 100644 index 00000000000..93523559288 --- /dev/null +++ b/development/libs/barrel/docs/blue-penguin/templates/article_stub.html @@ -0,0 +1,37 @@ + {% if not articles_page or first_article_of_day %} +

{{ article.date.strftime("%b %d, %Y") }}

+ {% endif %} + +
+ {% if article.title %} +

+ {{ article.title }} +

+ {% endif %} + + {% if not articles_page %} + {% include "translations.html" %} + {% endif %} + + + {{ article.content }} +
+ +
+ posted at {{ article.date.strftime("%H:%M") }} + {% if article.category.name != "misc" %} +  ·  + {% endif %} + {% if article.tags %} +  · + {% for t in article.tags %} +  {{ t }} + {% endfor %} + {% endif %} +
+ {% if articles_page and DISQUS_SITENAME %} + Click to read and post comments + {% else %} + {% include "disqus.html" %} + {% endif %} +
diff --git a/development/libs/barrel/docs/blue-penguin/templates/author.html b/development/libs/barrel/docs/blue-penguin/templates/author.html new file mode 100644 index 00000000000..b9ff61e6c04 --- /dev/null +++ b/development/libs/barrel/docs/blue-penguin/templates/author.html @@ -0,0 +1,7 @@ +{% extends "index.html" %} + +{% block title %}{{ SITENAME }} | Articles by {{ author }}{% endblock %} +{% block ephemeral_nav %} + + {{ ephemeral_nav_link(author, output_file, True) }} +{% endblock %} diff --git a/development/libs/barrel/docs/blue-penguin/templates/base.html b/development/libs/barrel/docs/blue-penguin/templates/base.html new file mode 100644 index 00000000000..378b12c2b0c --- /dev/null +++ b/development/libs/barrel/docs/blue-penguin/templates/base.html @@ -0,0 +1,105 @@ +{% macro ephemeral_nav_link(what, where, selected=False) -%} +
  • {{what}}
  • +{%- endmacro -%} + + + + + {% block head %} + + + {% block title %}{{ SITENAME }}{% endblock title %} + {# favicon #} + + + {% if FEED_ALL_ATOM %} + + {% endif %} + {% if FEED_ALL_RSS %} + + {% endif %} + {% if FEED_ATOM %} + + {% endif %} + {% if FEED_RSS %} + + {% endif %} + {% if CATEGORY_FEED_ATOM and category %} + + {% endif %} + {% if CATEGORY_FEED_RSS and category %} + + {% endif %} + {% if TAG_FEED_ATOM and tag %} + + {% endif %} + {% if TAG_FEED_RSS and tag %} + + {% endif %} + + + + + + + {% endblock head %} + + + {% if DISPLAY_HEADER or DISPLAY_HEADER is not defined %} +
    + {% if DISPLAY_MENU or DISPLAY_MENU is not defined %} + + {% endif %} +
    +

    {{ SITENAME }}

    + {% if SITESUBTITLE %} +

    {{ SITESUBTITLE }}

    + {% endif %} +
    +
    + {% endif %} +
    +
    + {%- block content -%}{%- endblock %} + + {% if DISPLAY_FOOTER or DISPLAY_FOOTER is not defined %} +
    + + {% endif %} +
    +
    +
    +{% include 'analytics.html' %} + + diff --git a/development/libs/barrel/docs/blue-penguin/templates/category.html b/development/libs/barrel/docs/blue-penguin/templates/category.html new file mode 100644 index 00000000000..23dea1f19d7 --- /dev/null +++ b/development/libs/barrel/docs/blue-penguin/templates/category.html @@ -0,0 +1,6 @@ +{% extends "index.html" %} +{% block title %}{{ SITENAME }} | articles in the "{{ category }}" category{% if articles_page.number != 0 %} | Page {{ articles_page.number }}{% endif %}{% endblock %} +{% block ephemeral_nav %} + + {{ ephemeral_nav_link(category, output_file, True) }} +{% endblock %} diff --git a/development/libs/barrel/docs/blue-penguin/templates/disqus.html b/development/libs/barrel/docs/blue-penguin/templates/disqus.html new file mode 100644 index 00000000000..b4093e556b6 --- /dev/null +++ b/development/libs/barrel/docs/blue-penguin/templates/disqus.html @@ -0,0 +1,12 @@ +{% if DISQUS_SITENAME %} +
    + + +{% endif %} diff --git a/development/libs/barrel/docs/blue-penguin/templates/index.html b/development/libs/barrel/docs/blue-penguin/templates/index.html new file mode 100644 index 00000000000..b8b40f4c045 --- /dev/null +++ b/development/libs/barrel/docs/blue-penguin/templates/index.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block title %}{{ SITENAME }}{% if articles_page.number != 1 %} | Page {{ articles_page.number }}{% endif %}{% endblock %} +{% block content %} +{% set date = None %} +{% for article in articles_page.object_list %} +{% if date != article.date.date() %} +{% set first_article_of_day = True %} +{% else %} +{% set first_article_of_day = False %} +{% endif %} +{% set date = article.date.date() %} +{% include "article_stub.html" %} +{% endfor %} + +{% include "pagination.html" %} +{% endblock %} diff --git a/development/libs/barrel/docs/blue-penguin/templates/page.html b/development/libs/barrel/docs/blue-penguin/templates/page.html new file mode 100644 index 00000000000..94b9610059c --- /dev/null +++ b/development/libs/barrel/docs/blue-penguin/templates/page.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} + +{% block content %} + +
    + {{ page.content }} +
    +{% endblock %} diff --git a/development/libs/barrel/docs/blue-penguin/templates/pagination.html b/development/libs/barrel/docs/blue-penguin/templates/pagination.html new file mode 100644 index 00000000000..69eac104e99 --- /dev/null +++ b/development/libs/barrel/docs/blue-penguin/templates/pagination.html @@ -0,0 +1,38 @@ +{# Use PAGINATION_PATTERNS or pagination may break #} +{% if DEFAULT_PAGINATION and (articles_page.has_previous() or articles_page.has_next()) %} + +
    +
    + {% if PAGINATION_PATTERNS[0][0] != 0 %} + {%- if articles_page.has_previous() %} + {% if articles_page.previous_page_number() == 1 %} + + ← Previous + {%- else %} + + ← Previous + {%- endif %} + {%- endif %} + {%- if articles_page.has_next() %} + + Next → + {%- endif %} + {% else %} + {%- if articles_page.has_previous() %} + {% if articles_page.previous_page_number() == 1 %} + + ← Previous + {%- else %} + + ← Previous + {%- endif %} + {%- endif %} + {%- if articles_page.has_next() %} + + Next → + {%- endif %} + {% endif %} + + Page {{ articles_page.number }} of {{ articles_paginator.num_pages }} +
    +{% endif %} diff --git a/development/libs/barrel/docs/blue-penguin/templates/tag.html b/development/libs/barrel/docs/blue-penguin/templates/tag.html new file mode 100644 index 00000000000..92c3439253d --- /dev/null +++ b/development/libs/barrel/docs/blue-penguin/templates/tag.html @@ -0,0 +1,5 @@ +{% extends "index.html" %} +{% block title %}{{ SITENAME }} | articles tagged "{{ tag }}"{% if articles_page.number != 1 %} | Page {{ articles_page.number }}{% endif %}{% endblock %} +{% block ephemeral_nav %} + {{ ephemeral_nav_link(tag, output_file, True) }} +{% endblock %} diff --git a/development/libs/barrel/docs/blue-penguin/templates/tags.html b/development/libs/barrel/docs/blue-penguin/templates/tags.html new file mode 100644 index 00000000000..ac657229bac --- /dev/null +++ b/development/libs/barrel/docs/blue-penguin/templates/tags.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} +{% block content %} +
      + {% for tag, articles in tags %} +
    • {{ tag }}
    • + {% endfor %} +
    +{% endblock %} diff --git a/development/libs/barrel/docs/blue-penguin/templates/translations.html b/development/libs/barrel/docs/blue-penguin/templates/translations.html new file mode 100644 index 00000000000..f0a0fa2af3f --- /dev/null +++ b/development/libs/barrel/docs/blue-penguin/templates/translations.html @@ -0,0 +1,6 @@ +{% if article.translations %} +Translations: + {% for translation in article.translations %} + {{ translation.lang }} + {% endfor %} +{% endif %} \ No newline at end of file diff --git a/development/libs/barrel/docs/content/blog/001_hello_barrel.md b/development/libs/barrel/docs/content/blog/001_hello_barrel.md new file mode 100644 index 00000000000..ddb7976aa64 --- /dev/null +++ b/development/libs/barrel/docs/content/blog/001_hello_barrel.md @@ -0,0 +1,59 @@ +Title: Releasing 0.5.0 +Category: Blog +Date: 2019-03-21 21:30 + +So `barrel.rs` has a blog now πŸŽ‰! + +Today we are also happy to announce the release of version `0.5.0`. +A lot of work as gone into this release and breaks the API in a few ways. +This post will quickly highlight new features and also changes to the API. + +### New type system + +This release adds `barrel::types` which replaces the old `Column` types. +While types are still enums with different type variants, the creation +is now much more streamlined with a builder-style API. + +```rust +types::varchar(255) + .nullable(true) + .default("Alice") +``` + +This change allows us to more easily support database specific types +in the future and makes it easier for you to create your own custom +type builders. +Check out the `barrel::types` documentation for details. + +### Explicit IDs for tables + +Since `0.2.0` a new table had an implicit `PRIMARY` key called `id`. +It was brought up several times that `barrel` should not have implicit behaviour like this +and and as such we have no reverted this! + +Now you will need to create a new `id` field explicitly + +```rust +table.add_column("id", types::primary()) +``` + +We're still evaluating the option to add a `table.id()` function +(or similar) to make this process easier. + +### Various changes + +The `DatabaseExecutor` was renamed to `SqlRunner`, +there were several bug-fixes around `diesel` integration as well as +the table API. + +Most importantly, you will need a very recent `diesel` version +which then depends on `0.5.0`. +If you're using barrel currently, nothing will change until you update. +Be aware that your existing migrations will need adjustment if you plan +on re-running them! + +--- + +All in all, this is a pretty big step forward. +We're also tracking some things to implement on a roadmap +for an upcoming `1.0.0` release later this year (maybe!) diff --git a/development/libs/barrel/docs/content/images/logo.svg b/development/libs/barrel/docs/content/images/logo.svg new file mode 100644 index 00000000000..1f69f6d2e20 --- /dev/null +++ b/development/libs/barrel/docs/content/images/logo.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + SQL + + + + + diff --git a/development/libs/barrel/docs/content/pages/home.md b/development/libs/barrel/docs/content/pages/home.md new file mode 100644 index 00000000000..8e1b7518808 --- /dev/null +++ b/development/libs/barrel/docs/content/pages/home.md @@ -0,0 +1,74 @@ +Title: barrel.rs +Subtitle: NULL +URL: / +Save_As: index.html +Template: page + +![](images/logo.svg) + +A 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. + +## Example + +The following example will help you get started + +```rust +use barrel::{types, Migration, Pg}; +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! + +## Unstable features + +Starting with `v0.2.4` `barrel` now has an `unstable` feature flag which will hide features and breaking changes that are in-development at the time of a minor or patch release. You can use these features if you so desire, but be aware that their usage will change more rapidely between versions (even patches) and their usage will be badly documented. + +## 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). + diff --git a/development/libs/barrel/docs/develop_server.sh b/development/libs/barrel/docs/develop_server.sh new file mode 100755 index 00000000000..8c2f27f0ac2 --- /dev/null +++ b/development/libs/barrel/docs/develop_server.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +## +# This section should match your Makefile +## +PY=${PY:-python} +PELICAN=${PELICAN:-pelican} +PELICANOPTS= + +BASEDIR=$(pwd) +INPUTDIR=$BASEDIR/content +OUTPUTDIR=$BASEDIR/output +CONFFILE=$BASEDIR/pelicanconf.py + +### +# Don't change stuff below here unless you are sure +### + +SRV_PID=$BASEDIR/srv.pid +PELICAN_PID=$BASEDIR/pelican.pid + +function usage(){ + echo "usage: $0 (stop) (start) (restart) [port]" + echo "This starts Pelican in debug and reload mode and then launches" + echo "an HTTP server to help site development. It doesn't read" + echo "your Pelican settings, so if you edit any paths in your Makefile" + echo "you will need to edit your settings as well." + exit 3 +} + +function alive() { + kill -0 $1 >/dev/null 2>&1 +} + +function shut_down(){ + PID=$(cat $SRV_PID) + if [[ $? -eq 0 ]]; then + if alive $PID; then + echo "Stopping HTTP server" + kill $PID + else + echo "Stale PID, deleting" + fi + rm $SRV_PID + else + echo "HTTP server PIDFile not found" + fi + + PID=$(cat $PELICAN_PID) + if [[ $? -eq 0 ]]; then + if alive $PID; then + echo "Killing Pelican" + kill $PID + else + echo "Stale PID, deleting" + fi + rm $PELICAN_PID + else + echo "Pelican PIDFile not found" + fi +} + +function start_up(){ + local port=$1 + echo "Starting up Pelican and HTTP server" + shift + $PELICAN --debug --autoreload -r $INPUTDIR -o $OUTPUTDIR -s $CONFFILE $PELICANOPTS & + pelican_pid=$! + echo $pelican_pid > $PELICAN_PID + cd $OUTPUTDIR + $PY -m pelican.server $port & + srv_pid=$! + echo $srv_pid > $SRV_PID + cd $BASEDIR + sleep 1 + if ! alive $pelican_pid ; then + echo "Pelican didn't start. Is the Pelican package installed?" + return 1 + elif ! alive $srv_pid ; then + echo "The HTTP server didn't start. Is there another service using port" $port "?" + return 1 + fi + echo 'Pelican and HTTP server processes now running in background.' +} + +### +# MAIN +### +[[ ($# -eq 0) || ($# -gt 2) ]] && usage +port='' +[[ $# -eq 2 ]] && port=$2 + +if [[ $1 == "stop" ]]; then + shut_down +elif [[ $1 == "restart" ]]; then + shut_down + start_up $port +elif [[ $1 == "start" ]]; then + if ! start_up $port; then + shut_down + fi +else + usage +fi diff --git a/development/libs/barrel/docs/fabfile.py b/development/libs/barrel/docs/fabfile.py new file mode 100755 index 00000000000..d46a16e0bc3 --- /dev/null +++ b/development/libs/barrel/docs/fabfile.py @@ -0,0 +1,94 @@ +from fabric.api import * +import fabric.contrib.project as project +import os +import shutil +import sys +import SocketServer + +from pelican.server import ComplexHTTPRequestHandler + +# Local path configuration (can be absolute or relative to fabfile) +env.deploy_path = 'output' +DEPLOY_PATH = env.deploy_path + +# Remote server configuration +production = 'spacekookie@lonelyrobot.io:22' +dest_path = '/var/www' + +# Rackspace Cloud Files configuration settings +env.cloudfiles_username = 'my_rackspace_username' +env.cloudfiles_api_key = 'my_rackspace_api_key' +env.cloudfiles_container = 'my_cloudfiles_container' + +# Github Pages configuration +env.github_pages_branch = "gh-pages" + +# Port for `serve` +PORT = 4000 + +def clean(): + """Remove generated files""" + if os.path.isdir(DEPLOY_PATH): + shutil.rmtree(DEPLOY_PATH) + os.makedirs(DEPLOY_PATH) + +def build(): + """Build local version of site""" + local('pelican -s pelicanconf.py') + +def rebuild(): + """`clean` then `build`""" + clean() + build() + +def regenerate(): + """Automatically regenerate site upon file modification""" + local('pelican -r -s pelicanconf.py') + +def serve(): + """Serve site at http://localhost:4000/""" + os.chdir(env.deploy_path) + + class AddressReuseTCPServer(SocketServer.TCPServer): + allow_reuse_address = True + + server = AddressReuseTCPServer(('', PORT), ComplexHTTPRequestHandler) + + sys.stderr.write('Serving on port {0} ...\n'.format(PORT)) + server.serve_forever() + +def reserve(): + """`build`, then `serve`""" + build() + serve() + +def preview(): + """Build production version of site""" + local('pelican -s publishconf.py') + +def cf_upload(): + """Publish to Rackspace Cloud Files""" + rebuild() + with lcd(DEPLOY_PATH): + local('swift -v -A https://auth.api.rackspacecloud.com/v1.0 ' + '-U {cloudfiles_username} ' + '-K {cloudfiles_api_key} ' + 'upload -c {cloudfiles_container} .'.format(**env)) + +@hosts(production) +def publish(): + """Publish to production via rsync""" + local('pelican -s publishconf.py') + project.rsync_project( + remote_dir=dest_path, + exclude=".DS_Store", + local_dir=DEPLOY_PATH.rstrip('/') + '/', + delete=True, + extra_opts='-c', + ) + +def gh_pages(): + """Publish to GitHub Pages""" + rebuild() + local("ghp-import -b {github_pages_branch} {deploy_path}".format(**env)) + local("git push origin {github_pages_branch}".format(**env)) diff --git a/development/libs/barrel/docs/pelicanconf.py b/development/libs/barrel/docs/pelicanconf.py new file mode 100755 index 00000000000..0d88a499ce4 --- /dev/null +++ b/development/libs/barrel/docs/pelicanconf.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- # +from __future__ import unicode_literals + +AUTHOR = 'Squirrel People' +SITENAME = 'barrel.rs' +SITEURL = 'https://rust-db.github.io/barrel' + +THEME = 'blue-penguin' + +EXTRA_PATH_METADATA = { + 'favicon.ico': {'path': 'favicon.ico'} +} + +TEMPLATE_DEBUG = True +DEBUG = True +READ_TIME = 180 + +PATH = 'content' +STATIC_PATHS = ['images'] +SITE_LOGO = 'favicon.ico' + +############################################# +############################################# + +DEFAULT_CATEGORY = 'Blog' +DEFAULT_DATE = 'fs' + +DISPLAY_CATEGORIES_ON_MENU = False +DISPLAY_PAGES_ON_MENU = False + +# use those if you want pelican standard pages to appear in your menu +MENU_INTERNAL_PAGES = ( + # ('Tags', TAGS_URL, TAGS_SAVE_AS), + # ('Authors', AUTHORS_URL, AUTHORS_SAVE_AS), + ('Blog', 'blog/', 'blog/index.html'), + # ('Archives', ARCHIVES_URL, ARCHIVES_SAVE_AS), +) + +MENUITEMS = ( +) + +ARTICLE_URL = '{category}/{slug}' +ARTICLE_SAVE_AS = '{category}/{slug}/index.html' + +PAGE_URL = '{slug}' +PAGE_SAVE_AS = '{slug}/index.html' + +CATEGORY_URL = '{slug}' +CATEGORY_SAVE_AS = '{slug}/index.html' + +TAG_URL = '{slug}' +TAG_SAVE_AS = '{slug}/index.html' + +############################################# +############################################# + +TIMEZONE = 'UTC' +DEFAULT_LANG = 'en' +LOCALE = 'C' +DEFAULT_DATE_FORMAT = '%Y-%m-%d' + +# Feed generation is usually not desired when developing +FEED_ALL_ATOM = None +CATEGORY_FEED_ATOM = None +TRANSLATION_FEED_ATOM = None +AUTHOR_FEED_ATOM = None + +FEED_RSS = 'rss.xml' +CATEGORY_FEED_RSS = '%s/rss.xml' + +JINJA_ENVIRONMENT = { + 'extensions': ['webassets.ext.jinja2.AssetsExtension', 'jinja2.ext.with_'] +} + +# DEFAULT_PAGINATION = 20 + diff --git a/development/libs/barrel/examples/pg_strings.rs b/development/libs/barrel/examples/pg_strings.rs new file mode 100644 index 00000000000..79715b426f0 --- /dev/null +++ b/development/libs/barrel/examples/pg_strings.rs @@ -0,0 +1,21 @@ +extern crate barrel; + +use barrel::backend::Pg; +use barrel::types; +use barrel::*; + +fn main() { + let mut m = Migration::new(); + + // A new table is automatically created with an "id" primary key + // To disable that call `without_id` on the return of `create_table` + m.create_table("users", |t: &mut Table| { + t.add_column("name", types::varchar(255)); // Default name is "Anonymous" + t.add_column("description", types::text().nullable(true)); // Can be null + t.add_column("age", types::integer()); + t.add_column("posts", types::foreign("posts", vec!["id", "url"])); + t.add_column("owns_plushy_sharks", types::boolean()); + }); + + println!("{}", m.make::()); +} diff --git a/development/libs/barrel/examples/sqlite_strings.rs b/development/libs/barrel/examples/sqlite_strings.rs new file mode 100644 index 00000000000..cac5e5031d8 --- /dev/null +++ b/development/libs/barrel/examples/sqlite_strings.rs @@ -0,0 +1,17 @@ +use barrel::backend::Sqlite; +use barrel::{types, Migration, Table}; + +fn main() { + let mut m = Migration::new(); + m.create_table("users", |t: &mut Table| { + t.add_column("id", types::text().primary(true)); + t.add_column("name", types::varchar(255).default("Anonymous")); // Default name is "Anonymous" + t.add_column("description", types::text().nullable(true)); // Can be null + t.add_column("age", types::integer()); + t.add_column("posts", types::foreign("posts", "id")); + t.add_column("created_at", types::date()); + t.add_column("owns_plushy_sharks", types::boolean()); + }); + + println!("{}", m.make::()); +} diff --git a/development/libs/barrel/examples/user_index.rs b/development/libs/barrel/examples/user_index.rs new file mode 100644 index 00000000000..f2fe6619184 --- /dev/null +++ b/development/libs/barrel/examples/user_index.rs @@ -0,0 +1,17 @@ +use barrel::{types, Migration}; + +fn main() { + let mut m = Migration::new(); + m.create_table("users", |t| { + t.add_column("first_name", types::varchar(64).nullable(false)); + t.add_column("last_name", types::varchar(64).nullable(false)); + t.add_column("birthday", types::date().nullable(false)); + + t.add_index( + "names", + types::index(vec!["first_name", "last_name"]) + .unique(true) + .nullable(false), + ); + }); +} diff --git a/development/libs/barrel/guides/diesel-setup.md b/development/libs/barrel/guides/diesel-setup.md new file mode 100644 index 00000000000..69f71ab96ec --- /dev/null +++ b/development/libs/barrel/guides/diesel-setup.md @@ -0,0 +1,48 @@ +# 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" +``` + +**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.3", features = ["sqlite3"] } +# ... +``` + +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. diff --git a/development/libs/barrel/shell.nix b/development/libs/barrel/shell.nix new file mode 100644 index 00000000000..ae46c4589ca --- /dev/null +++ b/development/libs/barrel/shell.nix @@ -0,0 +1,8 @@ +with import {}; + +stdenv.mkDerivation { + name = "qaul"; + buildInputs = with pkgs; [ + rustracer rustup clangStdenv + ]; +} diff --git a/development/libs/barrel/src/TODO b/development/libs/barrel/src/TODO new file mode 100644 index 00000000000..716a7782cbe --- /dev/null +++ b/development/libs/barrel/src/TODO @@ -0,0 +1,42 @@ +The 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 diff --git a/development/libs/barrel/src/backend/mod.rs b/development/libs/barrel/src/backend/mod.rs new file mode 100644 index 00000000000..107b718a717 --- /dev/null +++ b/development/libs/barrel/src/backend/mod.rs @@ -0,0 +1,89 @@ +//! A backend module which provides a few generic traits +//! to implement SQL generation for different databases. +//! +//! It also re-exports the generators for existing databases +//! so they can be used more conveniently. + +#[cfg(feature = "mysql")] +mod mysql; +#[cfg(feature = "mysql")] +pub use self::mysql::MySql; + +#[cfg(feature = "pg")] +mod pg; +#[cfg(feature = "pg")] +pub use self::pg::Pg; + +#[cfg(feature = "sqlite3")] +mod sqlite3; +#[cfg(feature = "sqlite3")] +pub use self::sqlite3::Sqlite; + +#[allow(unused_imports)] +use crate::{types::Type, Migration}; + +/// An enum describing all supported Sql flavours +#[derive(Copy, Clone, Debug)] +pub enum SqlVariant { + #[cfg(feature = "sqlite3")] + Sqlite, + #[cfg(feature = "pg")] + Pg, + #[cfg(feature = "mysql")] + Mysql, + #[doc(hidden)] + __Empty, +} + +impl SqlVariant { + pub(crate) fn run_for(self, _migr: &Migration) -> String { + match self { + #[cfg(feature = "sqlite3")] + SqlVariant::Sqlite => _migr.make::(), + + #[cfg(feature = "pg")] + SqlVariant::Pg => _migr.make::(), + + #[cfg(feature = "mysql")] + SqlVariant::Mysql => _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; + + /// Drop a multi-column index + fn drop_index(name: &str) -> String; +} diff --git a/development/libs/barrel/src/backend/mysql.rs b/development/libs/barrel/src/backend/mysql.rs new file mode 100644 index 00000000000..5b27c69e334 --- /dev/null +++ b/development/libs/barrel/src/backend/mysql.rs @@ -0,0 +1,162 @@ +//! MySQL implementation of a generator +//! +//! This module generates strings that are specific to MySQL +//! databases. They should be thoroughly tested via unit testing + +use super::SqlGenerator; +use crate::types::{BaseType, Type}; + +/// A simple macro that will generate a schema prefix if it exists +macro_rules! prefix { + ($schema:expr) => { + $schema + .map(|s| format!("`{}`.", s)) + .unwrap_or_else(|| String::new()) + }; +} + +/// MySQL generator backend +pub struct MySql; +impl SqlGenerator for MySql { + fn create_table(name: &str, schema: Option<&str>) -> String { + format!("CREATE TABLE {}`{}`", prefix!(schema), name) + } + + fn create_table_if_not_exists(name: &str, schema: Option<&str>) -> String { + format!("CREATE TABLE {}`{}` IF NOT EXISTS", prefix!(schema), name) + } + + fn drop_table(name: &str, schema: Option<&str>) -> String { + format!("DROP TABLE {}`{}`", prefix!(schema), name) + } + + fn drop_table_if_exists(name: &str, schema: Option<&str>) -> String { + format!("DROP TABLE {}`{}` IF EXISTS", prefix!(schema), name) + } + + fn rename_table(old: &str, new: &str, schema: Option<&str>) -> String { + let schema = prefix!(schema); + format!("RENAME TABLE {}`{}` TO {}`{}`", schema, old, schema, new) + } + + fn alter_table(name: &str, schema: Option<&str>) -> String { + format!("ALTER TABLE {}`{}`", prefix!(schema), name) + } + + fn add_column(ex: bool, schema: Option<&str>, name: &str, tt: &Type) -> String { + let bt: BaseType = tt.get_inner(); + use self::BaseType::*; + let name = format!("`{}`", name); + + #[cfg_attr(rustfmt, rustfmt_skip)] /* This shouldn't be formatted. It's too long */ + format!( + "{}{}{}{}{}", + match bt { + Text => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)), + Varchar(_) => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)), + Primary => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)), + Integer => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)), + Float => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)), + Double => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)), + UUID => unimplemented!(), + Json => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)), + Boolean => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)), + Date => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)), + Binary => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)), + Foreign(_, _, _) => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)), + Custom(_) => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(bt, schema)), + Array(it) => format!("{}{} {}", MySql::prefix(ex), name, MySql::print_type(Array(Box::new(*it)), schema)), + Index(_) => unreachable!(), + }, + match tt.primary { + true => " PRIMARY KEY", + false => "", + }, + match (&tt.default).as_ref() { + Some(ref m) => format!(" DEFAULT '{}'", m), + _ => format!(""), + }, + match tt.nullable { + true => "", + false => " NOT NULL", + }, + match tt.unique { + true => " UNIQUE", + false => "", + }, + ) + } + + fn drop_column(name: &str) -> String { + format!("DROP COLUMN `{}`", name) + } + + fn rename_column(old: &str, new: &str) -> String { + format!("CHANGE COLUMN `{}` `{}`", old, new) + } + + fn create_index(table: &str, schema: Option<&str>, name: &str, _type: &Type) -> String { + // FIXME: Implement Mysql specific index builder here + format!( + "CREATE {} INDEX `{}` ON {}`{}` ({})", + match _type.unique { + true => "UNIQUE", + false => "", + }, + name, + prefix!(schema), + table, + match _type.inner { + BaseType::Index(ref cols) => cols + .iter() + .map(|col| format!("`{}`", col)) + .collect::>() + .join(", "), + _ => unreachable!(), + } + ) + } + + fn drop_index(name: &str) -> String { + format!("DROP INDEX `{}`", name) + } +} + +impl MySql { + fn prefix(ex: bool) -> String { + match ex { + true => format!("ADD COLUMN "), + false => format!(""), + } + } + + fn print_type(t: BaseType, schema: Option<&str>) -> String { + use self::BaseType::*; + match t { + Text => format!("TEXT"), + Varchar(l) => match l { + 0 => format!("VARCHAR"), // For "0" remove the limit + _ => format!("VARCHAR({})", l), + }, + /* "NOT NULL" is added here because normally primary keys are implicitly not-null */ + Primary => format!("INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY"), + Integer => format!("INTEGER"), + Float => format!("FLOAT"), + Double => format!("DOUBLE"), + UUID => format!("CHAR(36)"), + Boolean => format!("BOOLEAN"), + Date => format!("DATE"), + Json => format!("JSON"), + Binary => format!("BYTEA"), + Foreign(s, t, refs) => format!( + "INTEGER REFERENCES {}{}({})", + prefix!(s), + t, + refs.0.join(",") + ), + Custom(t) => format!("{}", t), + Array(meh) => format!("{}[]", MySql::print_type(*meh, schema)), + Index(_) => unreachable!(), + } + } +} diff --git a/development/libs/barrel/src/backend/pg.rs b/development/libs/barrel/src/backend/pg.rs new file mode 100644 index 00000000000..1bea666efa1 --- /dev/null +++ b/development/libs/barrel/src/backend/pg.rs @@ -0,0 +1,164 @@ +//! Postgres implementation of a generator +//! +//! This module generates strings that are specific to Postgres +//! databases. They should be thoroughly tested via unit testing + +use super::SqlGenerator; +use crate::types::{BaseType, Type}; + +/// A simple macro that will generate a schema prefix if it exists +macro_rules! prefix { + ($schema:expr) => { + $schema + .map(|s| format!("\"{}\".", s)) + .unwrap_or_else(|| String::new()) + }; +} + +/// Postgres SQL generator backend +pub struct Pg; +impl SqlGenerator for Pg { + fn create_table(name: &str, schema: Option<&str>) -> String { + format!("CREATE TABLE {}\"{}\"", prefix!(schema), name) + } + + fn create_table_if_not_exists(name: &str, schema: Option<&str>) -> String { + format!("CREATE TABLE IF NOT EXISTS {}\"{}\"", prefix!(schema), name) + } + + fn drop_table(name: &str, schema: Option<&str>) -> String { + format!("DROP TABLE {}\"{}\"", prefix!(schema), name) + } + + fn drop_table_if_exists(name: &str, schema: Option<&str>) -> String { + format!("DROP TABLE IF EXISTS {}\"{}\"", prefix!(schema), name) + } + + fn rename_table(old: &str, new: &str, schema: Option<&str>) -> String { + let schema = prefix!(schema); + format!( + "ALTER TABLE {}\"{}\" RENAME TO {}\"{}\"", + schema, old, schema, new + ) + } + + fn alter_table(name: &str, schema: Option<&str>) -> String { + format!("ALTER TABLE {}\"{}\"", prefix!(schema), name) + } + + fn add_column(ex: bool, schema: Option<&str>, name: &str, tt: &Type) -> String { + let bt: BaseType = tt.get_inner(); + use self::BaseType::*; + + #[cfg_attr(rustfmt, rustfmt_skip)] /* This shouldn't be formatted. It's too long */ + format!( + "{}{}{}{}{}", + match bt { + Text => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)), + Varchar(_) => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)), + Primary => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)), + Integer => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)), + Float => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)), + Double => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)), + UUID => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)), + Json => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)), + Boolean => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)), + Date => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)), + Binary => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)), + Foreign(_, _, _) => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)), + Custom(_) => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(bt, schema)), + Array(it) => format!("{}\"{}\" {}", Pg::prefix(ex), name, Pg::print_type(Array(Box::new(*it)), schema)), + Index(_) => unreachable!(), // Indices are handled via custom builder + }, + match tt.primary { + true => " PRIMARY KEY", + false => "", + }, + match (&tt.default).as_ref() { + Some(ref m) => format!(" DEFAULT '{}'", m), + _ => format!(""), + }, + match tt.nullable { + true => "", + false => " NOT NULL", + }, + match tt.unique { + true => " UNIQUE", + false => "", + }, + ) + } + + fn drop_column(name: &str) -> String { + format!("DROP COLUMN \"{}\"", name) + } + + fn rename_column(old: &str, new: &str) -> String { + format!("ALTER COLUMN \"{}\" RENAME TO \"{}\"", old, new) + } + + fn create_index(table: &str, schema: Option<&str>, name: &str, _type: &Type) -> String { + // FIXME: Implement PG specific index builder here + format!( + "CREATE {} INDEX \"{}\" ON {}\"{}\" ({})", + match _type.unique { + true => "UNIQUE", + false => "", + }, + name, + prefix!(schema), + table, + match _type.inner { + BaseType::Index(ref cols) => cols + .iter() + .map(|col| format!("\"{}\"", col)) + .collect::>() + .join(", "), + _ => unreachable!(), + } + ) + } + + fn drop_index(name: &str) -> String { + format!("DROP INDEX \"{}\"", name) + } +} + +impl Pg { + fn prefix(ex: bool) -> String { + match ex { + true => format!("ADD COLUMN "), + false => format!(""), + } + } + + fn print_type(t: BaseType, schema: Option<&str>) -> String { + use self::BaseType::*; + match t { + Text => format!("TEXT"), + Varchar(l) => match l { + 0 => format!("VARCHAR"), // For "0" remove the limit + _ => format!("VARCHAR({})", l), + }, + /* "NOT NULL" is added here because normally primary keys are implicitly not-null */ + Primary => format!("SERIAL PRIMARY KEY NOT NULL"), + Integer => format!("INTEGER"), + Float => format!("FLOAT"), + Double => format!("DOUBLE PRECISION"), + UUID => format!("UUID"), + Boolean => format!("BOOLEAN"), + Date => format!("DATE"), + Json => format!("JSON"), + Binary => format!("BYTEA"), + Foreign(s, t, refs) => format!( + "INTEGER REFERENCES {}\"{}\"({})", + prefix!(s.or(schema.map(|s| s.into()))), + t, + refs.0.join(",") + ), + Custom(t) => format!("{}", t), + Array(meh) => format!("{}[]", Pg::print_type(*meh, schema)), + Index(_) => unreachable!(), // Indices are handled via custom builder + } + } +} diff --git a/development/libs/barrel/src/backend/sqlite3.rs b/development/libs/barrel/src/backend/sqlite3.rs new file mode 100644 index 00000000000..7c0d0438fa8 --- /dev/null +++ b/development/libs/barrel/src/backend/sqlite3.rs @@ -0,0 +1,155 @@ +//! Sqlite3 implementation of a generator + +use super::SqlGenerator; +use crate::types::{BaseType, Type}; + +/// A simple macro that will generate a schema prefix if it exists +macro_rules! prefix { + ($schema:expr) => { + $schema + .map(|s| format!("\"{}\".", s)) + .unwrap_or_else(|| String::new()) + }; +} + +/// We call this struct Sqlite instead of Sqlite3 because we hope not +/// to have to break the API further down the road +pub struct Sqlite; +impl SqlGenerator for Sqlite { + fn create_table(name: &str, schema: Option<&str>) -> String { + format!("CREATE TABLE {}\"{}\"", prefix!(schema), name) + } + + fn create_table_if_not_exists(name: &str, schema: Option<&str>) -> String { + format!("CREATE TABLE IF NOT EXISTS {}\"{}\"", prefix!(schema), name) + } + + fn drop_table(name: &str, schema: Option<&str>) -> String { + format!("DROP TABLE {}\"{}\"", prefix!(schema), name) + } + + fn drop_table_if_exists(name: &str, schema: Option<&str>) -> String { + format!("DROP TABLE IF EXISTS {}\"{}\"", prefix!(schema), name) + } + + fn rename_table(old: &str, new: &str, schema: Option<&str>) -> String { + let schema = prefix!(schema); + format!("ALTER TABLE {}\"{}\" RENAME TO \"{}\"", schema, old, new) + } + + fn alter_table(name: &str, schema: Option<&str>) -> String { + format!("ALTER TABLE {}\"{}\"", prefix!(schema), name) + } + + fn add_column(ex: bool, _: Option<&str>, name: &str, tt: &Type) -> String { + let bt: BaseType = tt.get_inner(); + use self::BaseType::*; + + #[cfg_attr(rustfmt, rustfmt_skip)] /* This shouldn't be formatted. It's too long */ + format!( + // SQL base - default - nullable - unique + "{}{}{}{}{}", + match bt { + Text => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)), + Varchar(_) => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)), + Primary => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)), + Integer => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)), + Float => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)), + Double => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)), + UUID => panic!("`UUID` not supported by Sqlite3. Use `Text` instead!"), + Json => panic!("`Json` not supported by Sqlite3. Use `Text` instead!"), + Boolean => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)), + Date => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)), + Binary => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)), + Foreign(_, _, _) => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)), + Custom(_) => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(bt)), + Array(it) => format!("{}\"{}\" {}", Sqlite::prefix(ex), name, Sqlite::print_type(Array(Box::new(*it)))), + Index(_) => unreachable!(), // Indices are handled via custom builders + }, + match tt.primary { + true => " PRIMARY KEY", + false => "", + }, + match (&tt.default).as_ref() { + Some(ref m) => format!(" DEFAULT '{}'", m), + _ => format!(""), + }, + match tt.nullable { + true => "", + false => " NOT NULL", + }, + match tt.unique { + true => " UNIQUE", + false => "", + } + ) + } + + /// Create a multi-column index + fn create_index(table: &str, schema: Option<&str>, name: &str, _type: &Type) -> String { + format!( + "CREATE {} INDEX {}\"{}\" ON \"{}\" ({});", + match _type.unique { + true => "UNIQUE", + false => "", + }, + prefix!(schema), + name, + table, + match _type.inner { + BaseType::Index(ref cols) => cols + .iter() + .map(|col| format!("\"{}\"", col)) + .collect::>() + .join(", "), + _ => unreachable!(), + } + ) + } + + /// Drop a multi-column index + fn drop_index(name: &str) -> String { + format!("DROP INDEX \"{}\"", name) + } + + fn drop_column(_: &str) -> String { + panic!("Sqlite does not support dropping columns!") + } + + fn rename_column(_: &str, _: &str) -> String { + panic!("Sqlite does not support renaming columns!") + } +} + +impl Sqlite { + fn prefix(ex: bool) -> String { + match ex { + true => format!("ADD COLUMN "), + false => format!(""), + } + } + + fn print_type(t: BaseType) -> String { + use self::BaseType::*; + match t { + Text => format!("TEXT"), + Varchar(l) => match l { + 0 => format!("VARCHAR"), // For "0" remove the limit + _ => format!("VARCHAR({})", l), + }, + Primary => format!("INTEGER NOT NULL PRIMARY KEY"), + Integer => format!("INTEGER"), + Float => format!("REAL"), + Double => format!("DOUBLE"), + UUID => unimplemented!(), + Boolean => format!("BOOLEAN"), + Date => format!("DATE"), + Json => panic!("Json is not supported by Sqlite3"), + Binary => format!("BINARY"), + Foreign(_, t, refs) => format!("INTEGER REFERENCES {}({})", t, refs.0.join(",")), + Custom(t) => format!("{}", t), + Array(meh) => format!("{}[]", Sqlite::print_type(*meh)), + Index(_) => unimplemented!(), + } + } +} diff --git a/development/libs/barrel/src/connectors.rs b/development/libs/barrel/src/connectors.rs new file mode 100644 index 00000000000..1aa3ff2ba00 --- /dev/null +++ b/development/libs/barrel/src/connectors.rs @@ -0,0 +1,19 @@ +//! 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); +} diff --git a/development/libs/barrel/src/integrations/diesel.rs b/development/libs/barrel/src/integrations/diesel.rs new file mode 100644 index 00000000000..f376c211fe7 --- /dev/null +++ b/development/libs/barrel/src/integrations/diesel.rs @@ -0,0 +1,197 @@ +//! + +// 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"); +} diff --git a/development/libs/barrel/src/integrations/mod.rs b/development/libs/barrel/src/integrations/mod.rs new file mode 100644 index 00000000000..04643489b5f --- /dev/null +++ b/development/libs/barrel/src/integrations/mod.rs @@ -0,0 +1,6 @@ +//! Include external integrations into frameworks and libraries +//! +//! + +#[cfg(feature = "diesel")] +pub mod diesel; diff --git a/development/libs/barrel/src/lib.rs b/development/libs/barrel/src/lib.rs new file mode 100644 index 00000000000..c11c2eb5c89 --- /dev/null +++ b/development/libs/barrel/src/lib.rs @@ -0,0 +1,176 @@ +//! 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 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 + }, + + /// Remove a multi-column index + RemoveIndex(String, String), +} diff --git a/development/libs/barrel/src/migration.rs b/development/libs/barrel/src/migration.rs new file mode 100644 index 00000000000..aab503f8fe3 --- /dev/null +++ b/development/libs/barrel/src/migration.rs @@ -0,0 +1,221 @@ +//! 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 (cols, indices) = 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 = cols.len(); + for (i, slice) in cols.iter().enumerate() { + sql.push_str(slice); + + if i < l - 1 { + sql.push_str(", "); + } + } + sql.push_str(")"); + + // Add additional index columns + if indices.len() > 0 { + sql.push_str(";"); + sql.push_str(&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 (cols, indices) = t.make::(true, schema); + sql.push_str(&T::alter_table(&t.meta.name(), schema)); + sql.push_str(" "); + let l = cols.len(); + for (i, slice) in cols.iter().enumerate() { + sql.push_str(slice); + + if i < l - 1 { + sql.push_str(", "); + } + } + + // Add additional index columns + if indices.len() > 0 { + sql.push_str(";"); + sql.push_str(&indices.join(";")); + } + } + } + + 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())); + } +} diff --git a/development/libs/barrel/src/schema.rs b/development/libs/barrel/src/schema.rs new file mode 100644 index 00000000000..f671b5505b5 --- /dev/null +++ b/development/libs/barrel/src/schema.rs @@ -0,0 +1,29 @@ +//! 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 { + +} + diff --git a/development/libs/barrel/src/table.rs b/development/libs/barrel/src/table.rs new file mode 100644 index 00000000000..1f6d98227fd --- /dev/null +++ b/development/libs/barrel/src/table.rs @@ -0,0 +1,177 @@ +//! 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; +use super::{IndexChange, 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") + } +} + +#[derive(Debug, Clone)] +pub struct Table { + pub meta: TableMeta, + columns: Vec, + indices: Vec, +} + +impl Table { + pub fn new>(name: S) -> Self { + Self { + meta: TableMeta::new(name.into()), + columns: vec![], + indices: vec![], + } + } + + /// 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, + }); + } + + /// 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(), + )); + } + + /// Generate Sql for this table, returned as two vectors + /// + /// The first vector (`.0`) represents all column changes done to the table, + /// the second vector (`.1`) contains all index and suffix changes. + /// + /// It is very well possible for either of them to be empty, + /// although both being empty *might* signify an error. + pub fn make( + &mut self, + ex: bool, + schema: Option<&str>, + ) -> (Vec, Vec) { + use IndexChange as IC; + 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 indeces = self + .indices + .iter() + .map(|change| match change { + IC::AddIndex { + index, + table, + columns, + } => T::create_index(table, schema, index, columns), + IC::RemoveIndex(_, index) => T::drop_index(index), + }) + .collect(); + + (columns, indeces) + } +} + +/// 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 + } +} diff --git a/development/libs/barrel/src/tests/common/cloning.rs b/development/libs/barrel/src/tests/common/cloning.rs new file mode 100644 index 00000000000..55fad482249 --- /dev/null +++ b/development/libs/barrel/src/tests/common/cloning.rs @@ -0,0 +1,91 @@ +use 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); +} diff --git a/development/libs/barrel/src/tests/common/mod.rs b/development/libs/barrel/src/tests/common/mod.rs new file mode 100644 index 00000000000..bf3aa2a7af1 --- /dev/null +++ b/development/libs/barrel/src/tests/common/mod.rs @@ -0,0 +1,129 @@ +/* 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() +// ); +// } diff --git a/development/libs/barrel/src/tests/common/runtime.rs b/development/libs/barrel/src/tests/common/runtime.rs new file mode 100644 index 00000000000..17ddb092bd4 --- /dev/null +++ b/development/libs/barrel/src/tests/common/runtime.rs @@ -0,0 +1,19 @@ +//! 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); +} diff --git a/development/libs/barrel/src/tests/common/utils.rs b/development/libs/barrel/src/tests/common/utils.rs new file mode 100644 index 00000000000..2a1a55dcd5c --- /dev/null +++ b/development/libs/barrel/src/tests/common/utils.rs @@ -0,0 +1,16 @@ +use 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); +} diff --git a/development/libs/barrel/src/tests/mod.rs b/development/libs/barrel/src/tests/mod.rs new file mode 100644 index 00000000000..de93522c241 --- /dev/null +++ b/development/libs/barrel/src/tests/mod.rs @@ -0,0 +1,13 @@ +//! 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; diff --git a/development/libs/barrel/src/tests/mysql/add_column.rs b/development/libs/barrel/src/tests/mysql/add_column.rs new file mode 100644 index 00000000000..b30832bbb6c --- /dev/null +++ b/development/libs/barrel/src/tests/mysql/add_column.rs @@ -0,0 +1,65 @@ +//! 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 + ); +} diff --git a/development/libs/barrel/src/tests/mysql/create_table.rs b/development/libs/barrel/src/tests/mysql/create_table.rs new file mode 100644 index 00000000000..4fa058c4515 --- /dev/null +++ b/development/libs/barrel/src/tests/mysql/create_table.rs @@ -0,0 +1,37 @@ +//! 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 `artist` IF NOT EXISTS (`id` INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` TEXT, `description` TEXT, `pic` TEXT, `mbid` TEXT);")); +} diff --git a/development/libs/barrel/src/tests/mysql/mod.rs b/development/libs/barrel/src/tests/mysql/mod.rs new file mode 100644 index 00000000000..4652152a1ab --- /dev/null +++ b/development/libs/barrel/src/tests/mysql/mod.rs @@ -0,0 +1,5 @@ +//! Test mysql generation + +mod add_column; +mod create_table; +mod simple; diff --git a/development/libs/barrel/src/tests/mysql/simple.rs b/development/libs/barrel/src/tests/mysql/simple.rs new file mode 100644 index 00000000000..9831718fb3b --- /dev/null +++ b/development/libs/barrel/src/tests/mysql/simple.rs @@ -0,0 +1,50 @@ +//! 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 `table_to_create` IF NOT EXISTS"), + 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 `table_to_drop` IF EXISTS"), 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); +} diff --git a/development/libs/barrel/src/tests/pg/add_column.rs b/development/libs/barrel/src/tests/pg/add_column.rs new file mode 100644 index 00000000000..5a98ae563e4 --- /dev/null +++ b/development/libs/barrel/src/tests/pg/add_column.rs @@ -0,0 +1,165 @@ +//! 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 + ); +} diff --git a/development/libs/barrel/src/tests/pg/create_table.rs b/development/libs/barrel/src/tests/pg/create_table.rs new file mode 100644 index 00000000000..edf0795a281 --- /dev/null +++ b/development/libs/barrel/src/tests/pg/create_table.rs @@ -0,0 +1,134 @@ +//! 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\";") + ); +} diff --git a/development/libs/barrel/src/tests/pg/mod.rs b/development/libs/barrel/src/tests/pg/mod.rs new file mode 100644 index 00000000000..10bafeccda1 --- /dev/null +++ b/development/libs/barrel/src/tests/pg/mod.rs @@ -0,0 +1,6 @@ +//! Test pgsql generation + +mod add_column; +mod create_table; +mod simple; +mod reference; \ No newline at end of file diff --git a/development/libs/barrel/src/tests/pg/reference.rs b/development/libs/barrel/src/tests/pg/reference.rs new file mode 100644 index 00000000000..4980e6d7b60 --- /dev/null +++ b/development/libs/barrel/src/tests/pg/reference.rs @@ -0,0 +1,19 @@ +#![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"); +} \ No newline at end of file diff --git a/development/libs/barrel/src/tests/pg/simple.rs b/development/libs/barrel/src/tests/pg/simple.rs new file mode 100644 index 00000000000..2968576225c --- /dev/null +++ b/development/libs/barrel/src/tests/pg/simple.rs @@ -0,0 +1,70 @@ +//! 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("ALTER COLUMN \"old_column\" RENAME TO \"new_column\""), + sql + ); +} diff --git a/development/libs/barrel/src/tests/sqlite3/create_table.rs b/development/libs/barrel/src/tests/sqlite3/create_table.rs new file mode 100644 index 00000000000..2d73998662d --- /dev/null +++ b/development/libs/barrel/src/tests/sqlite3/create_table.rs @@ -0,0 +1,37 @@ +//! 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);")); +} diff --git a/development/libs/barrel/src/tests/sqlite3/mod.rs b/development/libs/barrel/src/tests/sqlite3/mod.rs new file mode 100644 index 00000000000..f39c17f8cd8 --- /dev/null +++ b/development/libs/barrel/src/tests/sqlite3/mod.rs @@ -0,0 +1,4 @@ +//! A few simple tests for the sqlite3 string backend + +mod create_table; +mod simple; diff --git a/development/libs/barrel/src/tests/sqlite3/simple.rs b/development/libs/barrel/src/tests/sqlite3/simple.rs new file mode 100644 index 00000000000..8086b4ce7f6 --- /dev/null +++ b/development/libs/barrel/src/tests/sqlite3/simple.rs @@ -0,0 +1,56 @@ +//! 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); +} diff --git a/development/libs/barrel/src/types/builders.rs b/development/libs/barrel/src/types/builders.rs new file mode 100644 index 00000000000..43d44f76c51 --- /dev/null +++ b/development/libs/barrel/src/types/builders.rs @@ -0,0 +1,116 @@ +//! Builder API's module + +use super::impls::{BaseType, 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) +} + +/// 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 fixed-length string type +pub fn varchar(len: usize) -> Type { + Type::new(BaseType::Varchar(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) -> Type +where + S: Into, + I: Into>, +{ + Type::new(BaseType::Foreign(None, table.into(), keys.into())) +} + +/// 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) -> Type +where + S: Into, + I: Into>, +{ + Type::new(BaseType::Foreign( + Some(schema.into()), + table.into(), + keys.into(), + )) +} + +/// 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) +} + +/// 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: Vec) -> Type { + let vec: Vec = columns.into_iter().map(|s| s.into()).collect(); + Type::new(BaseType::Index(vec)) +} diff --git a/development/libs/barrel/src/types/defaults.rs b/development/libs/barrel/src/types/defaults.rs new file mode 100644 index 00000000000..ba3d75cb5ba --- /dev/null +++ b/development/libs/barrel/src/types/defaults.rs @@ -0,0 +1,89 @@ +use std::fmt::{self, Display, Formatter}; +use std::time::SystemTime; + +use super::Type; + +#[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), +} + +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), + } + ) + } +} + +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) + } +} diff --git a/development/libs/barrel/src/types/impls.rs b/development/libs/barrel/src/types/impls.rs new file mode 100644 index 00000000000..cfbb56b5ce9 --- /dev/null +++ b/development/libs/barrel/src/types/impls.rs @@ -0,0 +1,165 @@ +//! Implementation specifics for the type system + +use super::WrappedDefault; + +/// A smol wrapper around `Vec` to get around the orphan rules +#[derive(PartialEq, Debug, Clone)] +pub struct WrapVec(pub Vec); + +/// Core type enum, describing the basic type +#[derive(PartialEq, Debug, Clone)] +pub enum BaseType { + /// Strings + Text, + /// Like a String but worse + Varchar(usize), + /// Primary key (utility for incrementing integer – postgres supports this, we just mirror it) + Primary, + /// Simple integer + Integer, + /// 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 And Time + Date, + /// + Binary, + /// Foreign key to other table + Foreign(Option, String, WrapVec), + /// 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), +} + +/// 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,norun +/// 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 } + } +} + +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()) + } +} diff --git a/development/libs/barrel/src/types/mod.rs b/development/libs/barrel/src/types/mod.rs new file mode 100644 index 00000000000..ca018029133 --- /dev/null +++ b/development/libs/barrel/src/types/mod.rs @@ -0,0 +1,9 @@ +//! Types constructor module + +mod builders; +mod defaults; +mod impls; +pub use self::builders::*; + +pub use self::defaults::WrappedDefault; +pub use self::impls::{BaseType, Type, WrapVec};