commit | 52a2cab6eab8dc120be5965c14f4dccaecf419f5 | [log] [tgz] |
---|---|---|
author | Sean Griffin <sean@seantheprogrammer.com> | Mon Nov 30 10:29:37 2015 -0700 |
committer | Sean Griffin <sean@seantheprogrammer.com> | Mon Nov 30 10:43:41 2015 -0700 |
tree | 10e6269e8ddf2dc4dead4a3f657d30b085d81e5f | |
parent | b43f82b57ba7602778510bf44e704f910da5a764 [diff] |
Release 0.2 Yes, one day later. There were a couple of breaking changes that became clearly needed. I didn't expect this to get as much traffic as it has, so I'd like to get these changes made before too many people rely on the old behavior. See the CHANGELOG for specific API changes.
Diesel gets rid of the boilerplate for database interaction and eliminates runtime errors, without sacrificing performance. It takes full advantage of Rust's type system to create a low overhead query builder that “feels like Rust”.
We are not feature complete, nor do I think we‘ve covered all use cases. If you’ve found something difficult to accomplish, please open an issue.
Before you can do anything, you‘ll first need to set up your table. You’ll want to specify the columns and tables that exist using the table!
macro Once you've done that, you can already start using the query builder, and pulling out primitives.
Much of the behavior in diesel comes from traits, and it is recommended that you import diesel::*
. We avoid exporting generic type names, or any bare functions at that level.
#[macro_use] extern crate diesel; use diesel::*; table! { users { id -> Serial, name -> VarChar, favorite_color -> Nullable<VarChar>, } } fn users_with_name(connection: &Connection, target_name: &str) -> Vec<(i32, String, Option<String>)> { use self::users::dsl::*; users.filter(name.eq(target_name)) .load(connection) .unwrap() .collect() }
Note that we‘re importing users::dsl::*
here. This allows us to deal with only the users table, and not have to qualify everything. If we did not have this import, we’d need to put users::
before each column, and reference the table as users::table
.
If you want to be able to query for a struct, you'll need to implement the Queriable
trait Luckily, diesel_codegen can do this for us automatically.
#[derive(Queriable, Debug)] pub struct User { id: i32, name: String, favorite_color: Option<String>, } fn main() { let connection = Connection::establish(env!("DATABASE_URL")) .unwrap(); let users: Vec<User> = users::table.load(&connection) .unwrap().collect(); println!("Here are all the users in our database: {:?}", users); }
Inserting data requires implementing the Insertable
trait Once again, we can have this be automatically implemented for us by the compiler.
#[insertable_into(users)] struct NewUser<'a> { name: &'a str, favorite_color: Option<&'a str>, } fn create_user(connection: &Connection, name: &str, favorite_color: Option<&str>) -> QueryResult<User> { let new_user = NewUser { name: name, favorite_color: favorite_color, }; insert(&new_user).into(users::table).get_result(connection) }
insert
can return any struct which implements Queriable
for the right type. If you don‘t actually want to use the results, you should call execute
instead, or the compiler will complain that it can’t infer what type you meant to return. You can use the same struct for inserting and querying if you‘d like, but you’ll need to make columns that are not present during the insert optional (e.g. id
and timestamps). For this reason, you probably want to create a new struct instead.
You might notice that we're having to manually grab the first record that was inserted. This is because insert
can also take a slice or Vec
of records, and will insert them in a single query. For this reason, insert
will always return an Iterator
. A helper for this common case will likely be added in the future.
For both #[derive(Queriable)]
and #[insertable_into]
, you can annotate any single field with #[column_name="name"]
, if the name of your field differs from the name of the column. This annotation is required on all fields of tuple structs. This cannot be used, however, to work around name collisions with keywords that are reserved in Rust, as you cannot have a column with that name. This may change in the future.
#[insertable_into(users)] struct NewUser<'a>( #[column_name="name"] &'a str, #[column_name="favorite_color"] Option<&'a str>, ) fn create_user(connection: &Connection, name: &str, favorite_color: Option<&str>) -> QueryResult<User> { let new_user = NewUser(name, favorite_color); insert(&new_user).into(users::table).get_result(connection) }
To update a record, you‘ll need to call the update
function. Unlike insert
(which may change to use this pattern in the future), update
is a top level function which creates a query that you’ll later pass to the Connection
. Here's a simple example.
fn change_users_name(connection: &Connection, target: i32, new_name: &str) -> QueryResult<User> { use diesel::query_builder::update; use users::dsl::*; update(users.filter(id.eq(target))).set(name.eq(new_name)) .get_result(&connection) }
As with insert
, we can return any type which implements Queriable
for the right types. If you do not want to use the returned record(s), you should call execute
instead of run
or run_all
.
You can also use a struct to represent the changes, if it implements AsChangeset
. Again, diesel_codegen
can generate this for us automatically.
#[changeset_for(users)] pub struct UserChanges { name: String, favorite_color: Option<String>, } fn save_user(connection: &Connection, id: i32, changes: &UserChanges) -> QueryResult<User> { update(users::table.filter(users::id.eq(id))).set(changes) .get_result(&connection) }
Note that even though we've implemented AsChangeset
, we still need to specify what records we want to update. If the struct has the primary key on it, a method called save_changes
will also be added.
#[changeset_for(users)] pub struct User { id: i32, name: String, favorite_color: Option<String>, } fn change_name_to_jim(connection: &Connection, user: &mut User) -> QueryResult<()> { user.name = "Jim".into(); user.save_changes(connection) }
This method will update the model with any fields that are updated in the database (for example, if you have timestamps which are updated by triggers).
delete
works very similarly to update
, but does not support returning a record.
fn delete_user(connection: &Connection, user: User) -> QueryResult<()> { use diesel::query_builder::delete; use users::dsl::*; delete(users.filter(id.eq(user.id))).execute(&connection).unwrap(); debug_assert!(deleted_rows == 1); Ok(()) }
Take a look at the various files named on what you're trying to do in https://github.com/sgrif/diesel/tree/master/diesel_tests/tests. See https://github.com/sgrif/diesel/blob/master/diesel_tests/tests/schema.rs for how you can go about getting the data structures set up.