orbit_orm/raw.rs
1//! Raw SQL, for statements that the query builder does not cover. Each function takes a
2//! `&Database` or a `&mut Transaction`, as the query builder's terminal methods do.
3//!
4//! The SQL string is not escaped and is not checked. It must not contain user input. Put every
5//! value in `binds`, in the order of the `?` placeholders, and Orbit sends each one as a bind
6//! parameter:
7//!
8//! ```ignore
9//! let title: String = orbit_orm::raw::fetch_scalar(
10//! &db,
11//! "SELECT title FROM posts WHERE slug = ?",
12//! &[slug.into()],
13//! )
14//! .await?;
15//! ```
16//!
17//! Write `?` on every database. On PostgreSQL Orbit numbers the placeholders (`$1`, `$2`, ...)
18//! itself, and a `NULL` bind is written into the SQL as `NULL`, so it needs no type. A `?` inside
19//! a quoted string is not a placeholder.
20
21use crate::backend;
22use crate::driver::rows::DriverRows;
23use crate::transaction::Executor;
24use crate::{Error, Value};
25
26/// Runs a statement that changes rows, such as `UPDATE` or `DELETE`, and returns the rows
27/// affected. Use `insert` on `Query` for a plain insert, which also returns the id.
28pub async fn execute<'a>(db: impl Executor<'a>, sql: &str, binds: &[Value]) -> Result<u64, Error> {
29 let target = db.target();
30 let (sql, values) = target.driver().prepare(sql, binds);
31 Ok(backend::execute(target, &sql, &values).await?.rows_affected)
32}
33
34/// Runs a `SELECT` and returns every row, decoded as `T`. A tuple of column types or a struct
35/// that implements `FromRow` both work.
36pub async fn fetch_all<'a, T>(
37 db: impl Executor<'a>,
38 sql: &str,
39 binds: &[Value],
40) -> Result<Vec<T>, Error>
41where
42 T: DriverRows + Send + Unpin,
43{
44 let target = db.target();
45 let (sql, values) = target.driver().prepare(sql, binds);
46 Ok(backend::fetch_all::<T>(target, &sql, &values).await?)
47}
48
49/// Runs a `SELECT` and returns its first row, decoded as `T`, or `None` when no row matches.
50pub async fn fetch_optional<'a, T>(
51 db: impl Executor<'a>,
52 sql: &str,
53 binds: &[Value],
54) -> Result<Option<T>, Error>
55where
56 T: DriverRows + Send + Unpin,
57{
58 let target = db.target();
59 let (sql, values) = target.driver().prepare(sql, binds);
60 Ok(backend::fetch_optional::<T>(target, &sql, &values).await?)
61}
62
63/// Runs a `SELECT` that returns one column, such as `SELECT COUNT(*)`, and returns its first
64/// value as `T`. It is an error when no row comes back.
65///
66/// On MySQL, `LAST_INSERT_ID()` is per connection, so run it on the same connection as the
67/// insert: inside the same `Transaction`, not on the pool.
68pub async fn fetch_scalar<'a, T>(
69 db: impl Executor<'a>,
70 sql: &str,
71 binds: &[Value],
72) -> Result<T, Error>
73where
74 (T,): DriverRows + Send + Unpin,
75{
76 let target = db.target();
77 let (sql, values) = target.driver().prepare(sql, binds);
78 Ok(backend::scalar::<T>(target, &sql, &values).await?)
79}