Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 8 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,21 @@ For each SQL query annotated with a sqlc command, the plugin emits:
- A `const SQL: &str` holding the query text.
- A strongly-typed row struct (`QueryNameRow`) for `:one` / `:many`.
- An optional params struct (`QueryNameParams`) when a query has 2+ parameters.
- A `&mut self` method on `pub struct Queries<E>` that executes the query.
- A free `pub async fn` (or `pub fn` for batch streams) that executes the query, taking the executor as its first argument.

`Queries<E>` wraps anything implementing the generated `AsExecutor` trait. `AsExecutor` is implemented for `PgPool`, `&PgPool`, `PgConnection`, `Transaction<'_, Postgres>`, `PoolConnection<Postgres>`, and `&mut T` of each:
The executor argument is generic over the `AsExecutor` trait emitted in the same file. `AsExecutor` is implemented for `&PgPool`, `&mut PgConnection`, `&mut Transaction<'_, Postgres>`, `&mut PoolConnection<Postgres>`, and `&mut T` of each — i.e. the natural sqlx reference types:

```rust
// From a pool:
let mut q = Queries::new(&pool);
let author = q.get_author(1).await?;
let author = queries::get_author(&pool, 1).await?;

// Borrowed or owned pool connection:
// Pool connection:
let mut conn = pool.acquire().await?;
let mut q = Queries::new(&mut conn);
// ...or Queries::new(conn) to take ownership.
let author = queries::get_author(&mut conn, 1).await?;

// Transactions:
// Transaction:
let mut tx = pool.begin().await?;
let mut q = Queries::new(&mut tx);
q.delete_author(1).await?;
queries::delete_author(&mut tx, 1).await?;
tx.commit().await?;
```

Expand Down Expand Up @@ -132,7 +129,7 @@ Array types (`type[]`) become `Vec<T>`. Nullable columns become `Option<T>`.
| `:batchmany` | `impl Stream<Item = Result<Vec<QueryRow>, sqlx::Error>>` | Lazily fetch all rows per item |
| `:copyfrom` | `Result<u64, sqlx::Error>` | Chunked bulk insert from any `IntoIterator` |

All functions are `&mut self` methods on `Queries<E>`. The bound is `E: AsExecutor`, where `AsExecutor` is the trait emitted in each generated file. Impls cover `PgPool`, `&PgPool`, `PgConnection`, `Transaction<'_, Postgres>`, `PoolConnection<Postgres>`, and `&mut T` of each.
All functions are free `pub async fn` (or `pub fn` for batch streams) at module scope, taking the executor as their first argument. The bound is `E: AsExecutor`, where `AsExecutor` is the trait emitted in each generated file. Impls cover `&PgPool`, `&mut PgConnection`, `&mut Transaction<'_, Postgres>`, `&mut PoolConnection<Postgres>`, and `&mut T` of each.

Batch methods generate `Stream`-returning APIs and reference `futures_core` and `futures_util` directly. Consumer crates should include those dependencies alongside `sqlx`.

Expand Down
5 changes: 1 addition & 4 deletions examples/advanced-types/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
#[cfg(test)]
mod queries;
#[cfg(test)]
use queries::Queries;
#[cfg(test)]
use sqlx::{Connection as _, PgConnection};

#[cfg(test)]
Expand Down Expand Up @@ -41,8 +39,7 @@ async fn test_advanced_types_roundtrip() {
.await
.expect("insert");

let mut q = Queries::new(conn);
let event = q.get_event(1).await.expect("get");
let event = queries::get_event(&mut conn, 1).await.expect("get");
assert_eq!(event.name, "conference");
}

Expand Down
64 changes: 25 additions & 39 deletions examples/advanced-types/src/queries.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,11 @@
// Code generated by sqlc-gen-sqlx v0.1.5. DO NOT EDIT.
// Code generated by sqlc-gen-sqlx v0.1.7. DO NOT EDIT.
// sqlc version: v1.30.0

#![allow(
dead_code,
reason = "generated queries may expose items a caller does not use"
)]

const GET_EVENT: &str = "SELECT id, name, flags, event_window FROM events WHERE id = $1";
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct GetEventRow {
pub id: i64,
pub name: String,
pub flags: bit_vec::BitVec,
pub event_window: sqlx::postgres::types::PgRange<chrono::DateTime<chrono::Utc>>,
}
const LIST_EVENTS: &str = "SELECT id, name, flags, event_window FROM events";
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct ListEventsRow {
pub id: i64,
pub name: String,
pub flags: bit_vec::BitVec,
pub event_window: sqlx::postgres::types::PgRange<chrono::DateTime<chrono::Utc>>,
}
pub trait AsExecutor {
fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>;
}
Expand Down Expand Up @@ -55,28 +39,30 @@ impl<T: AsExecutor + ?Sized> AsExecutor for &mut T {
(**self).as_executor()
}
}
#[derive(Copy, Clone)]
pub struct Queries<E> {
db: E,
const GET_EVENT: &str = "SELECT id, name, flags, event_window FROM events WHERE id = $1";
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct GetEventRow {
pub id: i64,
pub name: String,
pub flags: bit_vec::BitVec,
pub event_window: sqlx::postgres::types::PgRange<chrono::DateTime<chrono::Utc>>,
}
impl<E> Queries<E> {
pub fn new(db: E) -> Self {
Self { db }
}
pub fn into_inner(self) -> E {
self.db
}
pub async fn get_event<E: AsExecutor>(mut db: E, id: i64) -> Result<GetEventRow, sqlx::Error> {
sqlx::query_as::<_, GetEventRow>(GET_EVENT)
.bind(id)
.fetch_one(db.as_executor())
.await
}
impl<E: AsExecutor> Queries<E> {
pub async fn get_event(&mut self, id: i64) -> Result<GetEventRow, sqlx::Error> {
sqlx::query_as::<_, GetEventRow>(GET_EVENT)
.bind(id)
.fetch_one(self.db.as_executor())
.await
}
pub async fn list_events(&mut self) -> Result<Vec<ListEventsRow>, sqlx::Error> {
sqlx::query_as::<_, ListEventsRow>(LIST_EVENTS)
.fetch_all(self.db.as_executor())
.await
}
const LIST_EVENTS: &str = "SELECT id, name, flags, event_window FROM events";
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct ListEventsRow {
pub id: i64,
pub name: String,
pub flags: bit_vec::BitVec,
pub event_window: sqlx::postgres::types::PgRange<chrono::DateTime<chrono::Utc>>,
}
pub async fn list_events<E: AsExecutor>(mut db: E) -> Result<Vec<ListEventsRow>, sqlx::Error> {
sqlx::query_as::<_, ListEventsRow>(LIST_EVENTS)
.fetch_all(db.as_executor())
.await
}
26 changes: 15 additions & 11 deletions examples/basic/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use sqlx::{Connection as _, PgConnection};
#[cfg(test)]
mod queries;
#[cfg(test)]
use queries::{CreateAuthorParams, Queries};
use queries::CreateAuthorParams;

#[cfg(test)]
#[tokio::test]
Expand All @@ -24,24 +24,28 @@ async fn test_author_roundtrip() {
.await
.unwrap();

let mut q = Queries::new(conn);

let author = q
.create_author(CreateAuthorParams {
let author = queries::create_author(
&mut conn,
CreateAuthorParams {
name: "Alice".to_string(),
bio: Some("Loves Rust".to_string()),
})
.await
.expect("create");
},
)
.await
.expect("create");
assert_eq!(author.name, "Alice");

let fetched = q.get_author(author.id).await.expect("get");
let fetched = queries::get_author(&mut conn, author.id)
.await
.expect("get");
assert_eq!(fetched.bio, Some("Loves Rust".to_string()));

let all = q.list_authors().await.expect("list");
let all = queries::list_authors(&mut conn).await.expect("list");
assert!(!all.is_empty());

let rows = q.delete_author_rows(author.id).await.expect("delete");
let rows = queries::delete_author_rows(&mut conn, author.id)
.await
.expect("delete");
assert_eq!(rows, 1);
}

Expand Down
138 changes: 62 additions & 76 deletions examples/basic/src/queries.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,11 @@
// Code generated by sqlc-gen-sqlx v0.1.5. DO NOT EDIT.
// Code generated by sqlc-gen-sqlx v0.1.7. DO NOT EDIT.
// sqlc version: v1.30.0

#![allow(
dead_code,
reason = "generated queries may expose items a caller does not use"
)]

const GET_AUTHOR: &str = "SELECT id, name, bio FROM authors WHERE id = $1";
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct GetAuthorRow {
pub id: i64,
pub name: String,
pub bio: Option<String>,
}
const LIST_AUTHORS: &str = "SELECT id, name, bio FROM authors ORDER BY name";
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct ListAuthorsRow {
pub id: i64,
pub name: String,
pub bio: Option<String>,
}
#[derive(Debug, Clone)]
pub struct CreateAuthorParams {
pub name: String,
pub bio: Option<String>,
}
const CREATE_AUTHOR: &str =
"INSERT INTO authors (name, bio) VALUES ($1, $2) RETURNING id, name, bio";
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct CreateAuthorRow {
pub id: i64,
pub name: String,
pub bio: Option<String>,
}
const DELETE_AUTHOR: &str = "DELETE FROM authors WHERE id = $1";
const DELETE_AUTHOR_ROWS: &str = "DELETE FROM authors WHERE id = $1";
pub trait AsExecutor {
fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>;
}
Expand Down Expand Up @@ -68,52 +39,67 @@ impl<T: AsExecutor + ?Sized> AsExecutor for &mut T {
(**self).as_executor()
}
}
#[derive(Copy, Clone)]
pub struct Queries<E> {
db: E,
const GET_AUTHOR: &str = "SELECT id, name, bio FROM authors WHERE id = $1";
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct GetAuthorRow {
pub id: i64,
pub name: String,
pub bio: Option<String>,
}
impl<E> Queries<E> {
pub fn new(db: E) -> Self {
Self { db }
}
pub fn into_inner(self) -> E {
self.db
}
pub async fn get_author<E: AsExecutor>(mut db: E, id: i64) -> Result<GetAuthorRow, sqlx::Error> {
sqlx::query_as::<_, GetAuthorRow>(GET_AUTHOR)
.bind(id)
.fetch_one(db.as_executor())
.await
}
impl<E: AsExecutor> Queries<E> {
pub async fn get_author(&mut self, id: i64) -> Result<GetAuthorRow, sqlx::Error> {
sqlx::query_as::<_, GetAuthorRow>(GET_AUTHOR)
.bind(id)
.fetch_one(self.db.as_executor())
.await
}
pub async fn list_authors(&mut self) -> Result<Vec<ListAuthorsRow>, sqlx::Error> {
sqlx::query_as::<_, ListAuthorsRow>(LIST_AUTHORS)
.fetch_all(self.db.as_executor())
.await
}
pub async fn create_author(
&mut self,
arg: CreateAuthorParams,
) -> Result<CreateAuthorRow, sqlx::Error> {
sqlx::query_as::<_, CreateAuthorRow>(CREATE_AUTHOR)
.bind(arg.name)
.bind(arg.bio)
.fetch_one(self.db.as_executor())
.await
}
pub async fn delete_author(&mut self, id: i64) -> Result<(), sqlx::Error> {
sqlx::query(DELETE_AUTHOR)
.bind(id)
.execute(self.db.as_executor())
.await?;
Ok(())
}
pub async fn delete_author_rows(&mut self, id: i64) -> Result<u64, sqlx::Error> {
let result = sqlx::query(DELETE_AUTHOR_ROWS)
.bind(id)
.execute(self.db.as_executor())
.await?;
Ok(result.rows_affected())
}
const LIST_AUTHORS: &str = "SELECT id, name, bio FROM authors ORDER BY name";
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct ListAuthorsRow {
pub id: i64,
pub name: String,
pub bio: Option<String>,
}
pub async fn list_authors<E: AsExecutor>(mut db: E) -> Result<Vec<ListAuthorsRow>, sqlx::Error> {
sqlx::query_as::<_, ListAuthorsRow>(LIST_AUTHORS)
.fetch_all(db.as_executor())
.await
}
#[derive(Debug, Clone)]
pub struct CreateAuthorParams {
pub name: String,
pub bio: Option<String>,
}
const CREATE_AUTHOR: &str =
"INSERT INTO authors (name, bio) VALUES ($1, $2) RETURNING id, name, bio";
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct CreateAuthorRow {
pub id: i64,
pub name: String,
pub bio: Option<String>,
}
pub async fn create_author<E: AsExecutor>(
mut db: E,
arg: CreateAuthorParams,
) -> Result<CreateAuthorRow, sqlx::Error> {
sqlx::query_as::<_, CreateAuthorRow>(CREATE_AUTHOR)
.bind(arg.name)
.bind(arg.bio)
.fetch_one(db.as_executor())
.await
}
const DELETE_AUTHOR: &str = "DELETE FROM authors WHERE id = $1";
pub async fn delete_author<E: AsExecutor>(mut db: E, id: i64) -> Result<(), sqlx::Error> {
sqlx::query(DELETE_AUTHOR)
.bind(id)
.execute(db.as_executor())
.await?;
Ok(())
}
const DELETE_AUTHOR_ROWS: &str = "DELETE FROM authors WHERE id = $1";
pub async fn delete_author_rows<E: AsExecutor>(mut db: E, id: i64) -> Result<u64, sqlx::Error> {
let result = sqlx::query(DELETE_AUTHOR_ROWS)
.bind(id)
.execute(db.as_executor())
.await?;
Ok(result.rows_affected())
}
10 changes: 2 additions & 8 deletions examples/batch/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ mod queries;
#[cfg(test)]
use futures_util::TryStreamExt;
#[cfg(test)]
use queries::Queries;
#[cfg(test)]
use sqlx::{Connection as _, PgConnection};

#[cfg(test)]
Expand Down Expand Up @@ -34,19 +32,15 @@ async fn test_batch_roundtrip() {
.await
.expect("insert");

let mut q = Queries::new(conn);

let authors: Vec<_> = q
.batch_get_author(vec![1, 2])
let authors: Vec<_> = queries::batch_get_author(&mut conn, vec![1, 2])
.try_collect()
.await
.expect("batch get");
assert_eq!(authors.len(), 2);
assert_eq!(authors[0].name, "Alice");
assert_eq!(authors[1].name, "Bob");

let _: Vec<()> = q
.batch_delete_author(vec![1, 2])
let _: Vec<()> = queries::batch_delete_author(&mut conn, vec![1, 2])
.try_collect()
.await
.expect("batch delete");
Expand Down
Loading
Loading