diff --git a/.env b/.env new file mode 100644 index 0000000..8b5960f --- /dev/null +++ b/.env @@ -0,0 +1 @@ +DATABASE_URL=postgresql://root@localhost:26257/diesel?application_name=cockroach&sslmode=disable diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9d37c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..10e867c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +authors = ["Tobias Schottdorf "] +name = "dieselroach" +version = "0.1.0" + +[dependencies] +dotenv = "*" + +[dependencies.diesel_codegen] +features = ["postgres"] +version = "*" + +[dependencies.diesel] +features = ["postgres"] +git = "https://github.com/diesel-rs/diesel" +rev = "0ce9179facd7defaef70b3b3563ba1c69dd27d48" diff --git a/README.md b/README.md new file mode 100644 index 0000000..8a7d650 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# Example of using CockroachDB with diesel + +This is almost verbatim the example from + +https://github.com/diesel-rs/diesel/tree/3ae353c3aff8a7ea64ed6cb39b3a045ac86cd60e/examples/postgres + +with minor adjustments: + +1. after `diesel setup`, edit the created `up.sql` and `down.sql` to contain + only a trivial statement (like `SELECT 1`). This is necessary because by + default they contain a [random trigger] that CockroachDB can't handle. +2. `schema.rs` was manually spelled out using the `table!` macro. The tutorial + uses `infer_schema!` for which we don't have all the [internals]. Note that + we use `BigInt` to properly support CockroachDB's `SERIAL` type. In turn, + `struct Post` has `id: i64` instead of `i32`. +3. Some adjustments in `show_posts` to list the ID which is otherwise impossible + to guess. +4. Pinned the diesel dependency for no good reason (to hopefully have this go + stale later). + +There are likely more problems not discovered by this toy example. See the +[tracking issue]. + +[random trigger]: https://github.com/diesel-rs/diesel/blob/master/diesel_cli/src/setup_sql/postgres/initial_setup/up.sql +[internals]: https://github.com/cockroachdb/cockroach/issues/8675 +[tracking issue]: https://github.com/cockroachdb/cockroach/issues/13787 diff --git a/migrations/.gitkeep b/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/migrations/00000000000000_diesel_initial_setup/down.sql b/migrations/00000000000000_diesel_initial_setup/down.sql new file mode 100644 index 0000000..e0ac49d --- /dev/null +++ b/migrations/00000000000000_diesel_initial_setup/down.sql @@ -0,0 +1 @@ +SELECT 1; diff --git a/migrations/00000000000000_diesel_initial_setup/up.sql b/migrations/00000000000000_diesel_initial_setup/up.sql new file mode 100644 index 0000000..02d27f3 --- /dev/null +++ b/migrations/00000000000000_diesel_initial_setup/up.sql @@ -0,0 +1,37 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + + + + +-- Sets up a trigger for the given table to automatically set a column called +-- `updated_at` whenever the row is modified (unless `updated_at` was included +-- in the modified columns) +-- +-- # Example +-- +-- ```sql +-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); +-- +-- SELECT diesel_manage_updated_at('users'); +-- ``` +-- CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ +-- BEGIN +-- EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s +-- FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); +-- END; +-- $$ LANGUAGE plpgsql; +-- +-- CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ +-- BEGIN +-- IF ( +-- NEW IS DISTINCT FROM OLD AND +-- NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at +-- ) THEN +-- NEW.updated_at := current_timestamp; +-- END IF; +-- RETURN NEW; +-- END; +-- $$ LANGUAGE plpgsql; +SELECT 1; diff --git a/migrations/2017-10-08-005753_trivialtest/down.sql b/migrations/2017-10-08-005753_trivialtest/down.sql new file mode 100644 index 0000000..1651d89 --- /dev/null +++ b/migrations/2017-10-08-005753_trivialtest/down.sql @@ -0,0 +1 @@ +DROP TABLE posts; diff --git a/migrations/2017-10-08-005753_trivialtest/up.sql b/migrations/2017-10-08-005753_trivialtest/up.sql new file mode 100644 index 0000000..e78af9e --- /dev/null +++ b/migrations/2017-10-08-005753_trivialtest/up.sql @@ -0,0 +1,7 @@ +-- Your SQL goes here +CREATE TABLE posts ( + id SERIAL PRIMARY KEY, + title VARCHAR NOT NULL, + body TEXT NOT NULL, + published BOOLEAN NOT NULL DEFAULT 'f' +); diff --git a/src/bin/delete_post.rs b/src/bin/delete_post.rs new file mode 100644 index 0000000..72761d4 --- /dev/null +++ b/src/bin/delete_post.rs @@ -0,0 +1,20 @@ +extern crate dieselroach; +extern crate diesel; + +use self::diesel::prelude::*; +use self::dieselroach::*; +use std::env::args; + +fn main() { + use dieselroach::schema::posts::dsl::*; + + let target = args().nth(1).expect("Expected a target to match against"); + let pattern = format!("%{}%", target); + + let connection = establish_connection(); + let num_deleted = diesel::delete(posts.filter(title.like(pattern))) + .execute(&connection) + .expect("Error deleting posts"); + + println!("Deleted {} posts", num_deleted); +} diff --git a/src/bin/publish_post.rs b/src/bin/publish_post.rs new file mode 100644 index 0000000..f75bb81 --- /dev/null +++ b/src/bin/publish_post.rs @@ -0,0 +1,21 @@ +extern crate dieselroach; +extern crate diesel; + +use self::diesel::prelude::*; +use self::dieselroach::*; +use self::dieselroach::models::Post; +use std::env::args; + +fn main() { + use dieselroach::schema::posts::dsl::{posts, published}; + + let id = args().nth(1).expect("publish_post requires a post id") + .parse::().expect("Invalid ID"); + let connection = establish_connection(); + + let post = diesel::update(posts.find(id)) + .set(published.eq(true)) + .get_result::(&connection) + .expect(&format!("Unable to find post {}", id)); + println!("Published post {}", post.title); +} diff --git a/src/bin/show_posts.rs b/src/bin/show_posts.rs new file mode 100644 index 0000000..fbdca69 --- /dev/null +++ b/src/bin/show_posts.rs @@ -0,0 +1,21 @@ +extern crate dieselroach; +extern crate diesel; + +use self::dieselroach::*; +use self::dieselroach::models::*; +use self::diesel::prelude::*; + +fn main() { + use dieselroach::schema::posts::dsl::*; + + let connection = establish_connection(); + let results = posts.load::(&connection) + .expect("Error loading posts"); + + println!("Displaying {} posts", results.len()); + for post in results { + println!("{}: {}{}", post.id, post.title, if post.published { "" } else { " (unpublished)" }); + println!("----------\n"); + println!("{}", post.body); + } +} diff --git a/src/bin/write_post.rs b/src/bin/write_post.rs new file mode 100644 index 0000000..be57ad9 --- /dev/null +++ b/src/bin/write_post.rs @@ -0,0 +1,26 @@ +extern crate dieselroach; +extern crate diesel; + +use self::dieselroach::*; +use std::io::{stdin, Read}; + +fn main() { + let connection = establish_connection(); + + println!("What would you like your title to be?"); + let mut title = String::new(); + stdin().read_line(&mut title).unwrap(); + let title = &title[..(title.len() - 1)]; // Drop the newline character + println!("\nOk! Let's write {} (Press {} when finished)\n", title, EOF); + let mut body = String::new(); + stdin().read_to_string(&mut body).unwrap(); + + let post = create_post(&connection, title, &body); + println!("\nSaved draft {} with id {}", title, post.id); +} + +#[cfg(not(windows))] +const EOF: &'static str = "CTRL+D"; + +#[cfg(windows)] +const EOF: &'static str = "CTRL+Z"; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..27c5c66 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,36 @@ +#[macro_use] extern crate diesel_codegen; +pub mod schema; +pub mod models; +#[macro_use] extern crate diesel; +extern crate dotenv; + +use diesel::prelude::*; +use diesel::pg::PgConnection; +use dotenv::dotenv; +use std::env; + +use self::models::{Post, NewPost}; + +pub fn establish_connection() -> PgConnection { + dotenv().ok(); + + let database_url = env::var("DATABASE_URL") + .expect("DATABASE_URL must be set"); + PgConnection::establish(&database_url) + .expect(&format!("Error connecting to {}", database_url)) +} + + +pub fn create_post<'a>(conn: &PgConnection, title: &'a str, body: &'a str) -> Post { + use schema::posts; + + let new_post = NewPost { + title: title, + body: body, + }; + + diesel::insert_into(posts::table) + .values(&new_post) + .get_result(conn) + .expect("Error saving new post") +} diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..eee545a --- /dev/null +++ b/src/models.rs @@ -0,0 +1,16 @@ +#[derive(Queryable)] +pub struct Post { + pub id: i64, + pub title: String, + pub body: String, + pub published: bool, +} + +use super::schema::posts; + +#[derive(Insertable)] +#[table_name="posts"] +pub struct NewPost<'a> { + pub title: &'a str, + pub body: &'a str, +} diff --git a/src/schema.rs b/src/schema.rs new file mode 100644 index 0000000..7719cc0 --- /dev/null +++ b/src/schema.rs @@ -0,0 +1,8 @@ +table! { + posts { + id -> BigInt, + title -> VarChar, + body -> Text, + published -> Bool, + } +}