Update 'databases' example README.

The README now more completely documents the example.

All implementations now make use of 'RETURNING'.
This commit is contained in:
Sergio Benitez 2023-08-25 15:19:15 -07:00
parent aa7805a5f8
commit fc76bf7b68
8 changed files with 105 additions and 31 deletions

View File

@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "INSERT INTO posts (title, text) VALUES (?, ?)",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "3c289da9873097a11191dbedc5c78d86afd6a6d36771bfeb12f331abca6279cf"
}

View File

@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "INSERT INTO posts (title, text) VALUES (?, ?) RETURNING id",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Int64"
}
],
"parameters": {
"Right": 2
},
"nullable": [
false
]
},
"hash": "bea4ef6e25064f6b383e854f8bc2770d89cfaf9859d0bfca78b2ca24627675b7"
}

View File

@ -7,7 +7,7 @@ publish = false
[dependencies] [dependencies]
rocket = { path = "../../core/lib", features = ["json"] } rocket = { path = "../../core/lib", features = ["json"] }
diesel = "2" diesel = { version = "2", features = ["returning_clauses_for_sqlite_3_35"] }
diesel_migrations = "2" diesel_migrations = "2"
[dependencies.sqlx] [dependencies.sqlx]

View File

@ -1,8 +1,69 @@
# Databases Example # Databases Example
This example makes use of SQLite. You'll need `sqlite3` and its development This example makes use of SQLite and MySQL. You'll need `sqlite3` and a MySQL
headers installed: client installed:
* **macOS:** `brew install sqlite` * **macOS:** `brew install sqlite mysql-client`
* **Debian**, **Ubuntu:** `apt-get install libsqlite3-dev` * **Debian**, **Ubuntu:** `apt-get install libsqlite3-dev libmysqlclient-dev`
* **Arch:** `pacman -S sqlite` * **Arch:** `pacman -S sqlite libmysqlclient`
## API Implementation
This example implements a JSON-based HTTP API for a "blog" using several database drivers:
* `sqlx` (`/sqlx`, `sqlx.rs`)
* `rusqlite` (`/rusqlite`, `rusqlite.rs`)
* `diesel` (sqlite) (`/diesel`, `diesel_sqlite.rs`)
* `diesel-async` (mysql) (`/diesel-async`, `diesel_mysql.rs`)
The exposed API is succinctly described as follows, with
[`httpie`](https://httpie.io/) CLI examples:
* `POST /driver`: create post via JSON with `title` and `text`; returns new
post JSON with new `id`
http http://127.0.0.1:8000/sqlx title="Title" text="Hello, world."
> { "id": 2128, "text": "Hello, world.", "title": "Title" }
* `GET /driver`: returns JSON array of IDs for blog posts
http http://127.0.0.1:8000/sqlx
> [ 2128, 2129, 2130, 2131 ]
* `GET /driver/<id>`: returns a JSON object for the post with id `<id>`
http http://127.0.0.1:8000/sqlx/2128
> { "id": 2128, "text": "Hello, world.", "title": "Title" }
* `DELETE /driver`: delete all posts
http delete http://127.0.0.1:8000/sqlx
* `DELETE /driver/<id>`: delete post with id `<id>`
http delete http://127.0.0.1:8000/sqlx/4
## Migrations
Database migrations are stored in the respective `db/${driver}` directory.
### `diesel`
Diesel migrations are found in `db/diesel/migrations`. They are run
automatically. They can be run manually as well:
```sh
cargo install diesel_cli --no-default-features --features sqlite
DATABASE_URL="db/diesel/db.sqlite" diesel migration --migration-dir db/diesel/migrations redo
```
### `sqlx`
sqlx migrations are found in `db/sqlx/migrations`. They are run automatically.
Query metadata for offline checking was prepared with the following commands:
```sh
cargo install sqlx-cli --no-default-features --features sqlite
DATABASE_URL="sqlite:$(pwd)/db/sqlx/db.sqlite" cargo sqlx prepare
```

View File

@ -92,6 +92,6 @@ async fn destroy(mut db: Connection<Db>) -> Result<()> {
pub fn stage() -> AdHoc { pub fn stage() -> AdHoc {
AdHoc::on_ignite("Diesel SQLite Stage", |rocket| async { AdHoc::on_ignite("Diesel SQLite Stage", |rocket| async {
rocket.attach(Db::init()) rocket.attach(Db::init())
.mount("/diesel-async/", routes![list, read, create, delete, destroy]) .mount("/diesel-async", routes![list, read, create, delete, destroy])
}) })
} }

View File

@ -32,14 +32,16 @@ table! {
} }
#[post("/", data = "<post>")] #[post("/", data = "<post>")]
async fn create(db: Db, post: Json<Post>) -> Result<Created<Json<Post>>> { async fn create(db: Db, mut post: Json<Post>) -> Result<Created<Json<Post>>> {
let post_value = post.clone(); let post_value = post.clone();
db.run(move |conn| { let id: Option<i32> = db.run(move |conn| {
diesel::insert_into(posts::table) diesel::insert_into(posts::table)
.values(&*post_value) .values(&*post_value)
.execute(conn) .returning(posts::id)
.get_result(conn)
}).await?; }).await?;
post.id = Some(id.expect("returning guarantees id present"));
Ok(Created::new("/").body(post)) Ok(Created::new("/").body(post))
} }

View File

@ -22,13 +22,15 @@ struct Post {
type Result<T, E = Debug<rusqlite::Error>> = std::result::Result<T, E>; type Result<T, E = Debug<rusqlite::Error>> = std::result::Result<T, E>;
#[post("/", data = "<post>")] #[post("/", data = "<post>")]
async fn create(db: Db, post: Json<Post>) -> Result<Created<Json<Post>>> { async fn create(db: Db, mut post: Json<Post>) -> Result<Created<Json<Post>>> {
let item = post.clone(); let item = post.clone();
db.run(move |conn| { let id = db.run(move |conn| {
conn.execute("INSERT INTO posts (title, text) VALUES (?1, ?2)", conn.query_row("INSERT INTO posts (title, text) VALUES (?1, ?2) RETURNING id",
params![item.title, item.text]) params![item.title, item.text],
|r| r.get(0))
}).await?; }).await?;
post.id = Some(id);
Ok(Created::new("/").body(post)) Ok(Created::new("/").body(post))
} }

View File

@ -23,12 +23,13 @@ struct Post {
} }
#[post("/", data = "<post>")] #[post("/", data = "<post>")]
async fn create(mut db: Connection<Db>, post: Json<Post>) -> Result<Created<Json<Post>>> { async fn create(mut db: Connection<Db>, mut post: Json<Post>) -> Result<Created<Json<Post>>> {
// There is no support for `RETURNING`. let query = sqlx::query! {
sqlx::query!("INSERT INTO posts (title, text) VALUES (?, ?)", post.title, post.text) "INSERT INTO posts (title, text) VALUES (?, ?) RETURNING id",
.execute(&mut **db) post.title, post.text
.await?; };
post.id = Some(query.fetch_one(&mut **db).await?.id);
Ok(Created::new("/").body(post)) Ok(Created::new("/").body(post))
} }