-
-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
972 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
# Create records | ||
|
||
Rwf can create model records in one of two ways: | ||
|
||
- `Model::save` method which is called on an instance of a struct implementing the `Model` trait | ||
- `Model::create` method which accepts the column names and their respective values as input | ||
|
||
## Saving models | ||
|
||
Using our `User` model from our [previous example](../), we can create a new record by instantiating a new instance of the `User` struct and calling `save`: | ||
|
||
```rust | ||
let user = User { | ||
id: None, | ||
email: "[email protected]".to_string(), | ||
created_at: OffsetDateTime::now_utc(), | ||
}; | ||
|
||
let user = user | ||
.save() | ||
.fetch(&mut conn) | ||
.await?; | ||
``` | ||
|
||
!!! note | ||
The `id` field is set to `None`. This ensures that the database | ||
assigns it a value automatically, and that this value is unique. | ||
|
||
Calling `save` on a model struct with the `id` set to `None` produces the following query: | ||
|
||
```postgresql | ||
INSERT INTO "users" ("email", "created_at") VALUES ($1, $2) RETURNING * | ||
``` | ||
|
||
## Using table defaults | ||
|
||
If you don't want to specify some columns when creating records and your database schema has configured defaults, you can use the `Model::create` | ||
method instead: | ||
|
||
=== "Rust" | ||
|
||
```rust | ||
let user = User::create(&[ | ||
("email", "[email protected]"), | ||
]) | ||
.fetch(&mut conn) | ||
.await? | ||
``` | ||
|
||
=== "SQL" | ||
|
||
```postgresql | ||
INSERT INTO "users" ("email") VALUES ($1) RETURNING * | ||
``` | ||
|
||
Any columns not specified in the `INSERT` statement will be automatically filled in with column defaults. For example, the `created_at` column | ||
specified in our [previous example](../) has a default value `NOW()`, the current database time. | ||
|
||
## Unique constraints | ||
|
||
It's very common to place unique constraints on certain columns in a table to avoid duplicate records. For example, the `"users"` table | ||
would typically have a unique contraint on the `email` column, ensuring that no two users have the same email address. | ||
|
||
To handle unique contraints, Rwf can update a record in-place if one exists already matching the constraint: | ||
|
||
=== "Rust" | ||
```rust | ||
let user = User::create(&[ | ||
("email", "[email protected]") | ||
]) | ||
.unique_by(&["email"]) | ||
.fetch(&mut conn) | ||
.await?; | ||
``` | ||
=== "SQL" | ||
```postgresql | ||
INSERT INTO "users" ("email") VALUES ($1) | ||
ON CONFLICT ("email") DO UPDATE | ||
SET "email" = EXCLUDED."email" | ||
RETURNING * | ||
``` | ||
|
||
## Optionally create records | ||
|
||
If the record matching the `INSERT` statement exists already, Rwf supports returning the exisitng row without performing an update: | ||
|
||
=== "Rust" | ||
```rust | ||
let user = User::find_or_create_by(&[ | ||
("email", "[email protected]") | ||
]) | ||
.fetch(&mut conn) | ||
.await?; | ||
``` | ||
=== "SQL" | ||
This executes _up to_ two queries, starting with: | ||
|
||
```postgresql | ||
SELECT * FROM "users" WHERE "email" = $1 | ||
``` | ||
|
||
If a row is returned, no more queries are executed. However, if no rows matching the condition exist, | ||
an `INSERT` query is executed: | ||
|
||
```postgresql | ||
INSERT INTO "users" ("email") VALUES ($1) RETURNING * | ||
``` | ||
|
||
### Combining with a unique constraint | ||
|
||
In busy web apps which execute thousands of queries per second, it's entirely possible for a record to be created between the time the `SELECT` query | ||
returns no rows and an `INSERT` query is sent to the database. In this case, a unique constraint violation error will be returned. To avoid this, | ||
it's possible to combine `unque_by` with `find_or_create_by` executed inside a single transaction: | ||
|
||
=== "Rust" | ||
```rust | ||
// Start a transaction explicitely. | ||
let transaction = Pool::transaction().await?; | ||
|
||
let user = User::find_or_create_by(&[ | ||
("email", "[email protected]") | ||
]) | ||
.unique_by(&["email"]) | ||
.fetch(&mut transaction) | ||
.await?; | ||
|
||
// Commit the transaction. | ||
transaction.commit().await?; | ||
``` | ||
=== "SQL" | ||
A transaction is started explicitely: | ||
```postgresql | ||
BEGIN; | ||
``` | ||
|
||
Afterwards, the ORM attempts to find a record matching the columns | ||
in the `INSERT` statement: | ||
|
||
```postgresql | ||
SELECT * FROM "users" WHERE "email" = $1 | ||
``` | ||
|
||
If this query returns a row, no more queries are executed. Otherwise, | ||
an `INSERT` query with `ON CONFLICT` clause is sent to the database: | ||
|
||
```postgresql | ||
INSERT INTO "users" ("email") VALUES ($1) | ||
ON CONFLICT ("email") DO UPDATE | ||
SET "email" = EXCLUDED."email" | ||
RETURNING * | ||
``` | ||
|
||
Finally, the transaction is committed to the database: | ||
|
||
```postgresql | ||
COMMIT; | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# Custom queries | ||
|
||
Sometimes the ORM is not enough and you need to write a complex query by hand. Rwf provides an easy way to execute arbitrary queries | ||
and map the results to a model struct: | ||
|
||
```rust | ||
let users = User::find_by_sql("SELECT * FROM users ORDER BY RANDOM() LIMIT 1") | ||
.fetch_all(&mut conn) | ||
.await?; | ||
``` | ||
|
||
!!! note | ||
Since this query is not generated by the ORM, you need to make sure | ||
to return all the necessary columns and correct data types to map the results to the Rust struct. | ||
|
||
|
||
## Use the database driver directly | ||
|
||
If you want to bypass the ORM entirely and just execute queries, you can do so by checking out a connection and calling the `query_cached` method on it: | ||
|
||
```rust | ||
let mut conn = Pool::connection().await?; | ||
|
||
let results = conn | ||
.query_cached("SELECT * FROM wherever WHERE column = $1", &[5]) | ||
.await?; | ||
``` | ||
|
||
Rwf uses `tokio_postgres` underneath to talk to Postgres, so you'll receive a `Vec<tokio_postgres::Row>` as a result of executing that function. | ||
|
||
Since `tokio_postgres` uses prepared statements, `query_cached` ensures that identical queries are not prepared more than once per connection. If you want | ||
to bypass that and use `tokio_postgres` directly, you can use `client()` instead: | ||
|
||
```rust | ||
let mut conn = Pool::connection().await?; | ||
|
||
// Returns a `tokio_postgres::Client` | ||
let client = conn.client(); | ||
|
||
client | ||
.execute("SELECT 1", &[]) | ||
.await?; | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# Debug queries | ||
|
||
When building queries using the ORM, the end result can be inspected by calling `to_sql()`: | ||
|
||
=== "Rust" | ||
```rust | ||
let query = User::all() | ||
.filter("created_at", Value::Null) | ||
.limit(5) | ||
.to_sql(); | ||
println!("Query: {}", query); | ||
``` | ||
=== "Output" | ||
``` | ||
Query: SELECT * FROM "users" WHERE "created_at" IS NULL LIMIT 5 | ||
``` | ||
|
||
The query will not be sent to the database, so it's safe to inspect all queries, no matter if they are performant or not. | ||
|
||
## Query plan | ||
|
||
Visual inspection of the query is often not sufficient to undertand query performance. For this purpose, databases like PostgreSQL provide | ||
the `EXPLAIN` functionality which, instead of executing the query, produces an execution plan: | ||
|
||
=== "Rust" | ||
```rust | ||
let plan = User::find(15) | ||
.explain(&mut conn) | ||
.await?; | ||
println!("{}", plan); | ||
``` | ||
=== "SQL" | ||
```postgresql | ||
EXPLAIN SELECT * FROM "users" WHERE "id" = $1 | ||
``` | ||
=== "Output" | ||
``` | ||
Seq Scan on users (cost=0.00..25.00 rows=6 width=40) | ||
Filter: (id = 5) | ||
``` | ||
|
||
When optimizing queries, this functionality is useful for finding queries that should be using indexes but perform a sequential scan instead. |
Oops, something went wrong.