10 releases (6 breaking)
0.7.0 | Jul 17, 2024 |
---|---|
0.6.1 | May 27, 2023 |
0.5.0 | Nov 14, 2022 |
0.4.0 | Jul 20, 2021 |
0.1.0 | Dec 29, 2020 |
#962 in Procedural macros
43 downloads per month
Used in 3 crates
(via butane)
295KB
7K
SLoC
Butane
An experimental ORM for Rust with a focus on simplicity and on writing Rust, not SQL
Butane takes an object-oriented approach to database operations. It may be thought of as much as an object-persistence system as an ORM -- the fact that it is backed by a SQL database is mostly an implementation detail to the API consumer.
Features
- Relational queries using Rust-like syntax (via
proc-macro
s) - Automatic migrations without writing SQL (although the generated SQL may be hand-tuned if necessary)
- Ability to embed migrations in Rust code (so that a library may easily bundle its migrations)
- SQLite and PostgreSQL backends
- Write entirely or nearly entirely the same code regardless of database backend
Getting Started
Models, declared with struct attributes define the database schema. For example the Post model for a blog might look like this:
#[model]
#[derive(Default)]
struct Post {
id: AutoPk<i32>,
title: String,
body: String,
published: bool,
likes: i32,
tags: Many<Tag>,
blog: ForeignKey<Blog>,
byline: Option<String>,
}
An object is an instance of a model. An object is created like a normal struct instance, but must be saved in order to be persisted.
let mut post = Post::new(blog, title, body);
post.save(conn)?;
Changes to the instance are only applied to the database when saved:
post.published = true;
post.save(conn)?;
Queries are performed ergonomically with the query!
macro.
let posts = query!(Post, published == true).limit(5).load(&conn)?;
For a detailed tutorial, see the Getting Started Guide.
Cargo Features
Butane exposes several features to Cargo. By default, no backends are
enabled: you will want to enable sqlite
and/or pg
:
default
: Turns ondatetime
,json
anduuid
.debug
: Used in developing Butane, not expected to be enabled by consumers.datetime
: Support for timestamps (usingchrono
crate).fake
: Support for thefake
crate's generation of fake data.json
: Support for storing structs as JSON, including using postgres'JSONB
field type.log
: Log certain warnings to thelog
crate facade (target "butane").pg
: Support for PostgreSQL usingpostgres
crate.r2d2
: Connection pooling usingr2d2
support (Seebutane::db::ConnectionManager
).sqlite
: Support for SQLite usingrusqlite
crate.sqlite-bundled
: Bundles sqlite instead of using the system version.tls
: Support for TLS when using PostgreSQL, usingpostgres-native-tls
crate.uuid
: Support for UUIDs (using theuuid
crate).
Limitations
- Butane, and its migration system especially, expects to own the database. It can be used with an existing database accessed also by other consumers, but it is not a design goal and there is no facility to infer butane models from an existing database schema.
- API ergonomics are prioritized above performance. This does not mean Butane is slow, but that when given a choice between a simple, straightforward API and eking out the smallest possible overhead, the API will win.
Roadmap
Butane is young. The following features are currently missing, but planned
- Foreign key constraint cascade setting
- Incremental object save
- Back-references for
ForeignKey
andMany
. - Field/column rename support in migrations
- Prepared/reusable queries
- Benchmarking and performance tuning
- Support for other databases such as MySQL or SQL Server are not explicitly planned, but contributions are welcome.
Comparison to Diesel
Butane is inspired by Diesel and by Django's ORM. If you're looking for a mature, performant, and flexible ORM, go use Diesel. Butane doesn't aim to be better than Diesel, but makes some different decisions, including:
-
It is more object-oriented, at the cost of flexibility.
-
Automatic migrations are prioritized.
-
Rust code is the source of truth. The schema is understood from the definition of Models in Rust code, rather than inferred from the database.
-
Queries are constructed using a DSL inside a
proc-macro
invocation rather than by importing DSL methods/names to use into the current scope. For Diesel, you might writeuse diesel_demo::schema::posts::dsl::*; let posts = posts.filter(published.eq(true)) .limit(5) .load::<Post>(&conn)?
whereas for Butane, you would instead write
let posts = query!(Post, published == true).limit(5).load(&conn)?;
Which form is preferable is primarily an aesthetic judgement.
-
Differences between database backends are largely hidden.
-
Diesel is overall significantly more mature and full-featured.
For a detailed tutorial, see the getting started guide.
License
Butane is licensed under either of the MIT license or the Apache License, Version 2.0 at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Butane by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Dependencies
~5–19MB
~262K SLoC