From 104d2b9cda087d508096f4888d312798c5670403 Mon Sep 17 00:00:00 2001 From: Randolf Jung Date: Sat, 9 May 2026 05:07:27 -0700 Subject: [PATCH] feat!: emit free functions instead of Queries methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the generated Queries wrapper with free functions at module scope. Each query becomes: pub async fn get_author(mut db: E, id: i64) -> ... pub fn batch_get_author<'a, E, I>(db: E, items: I) -> impl Stream + 'a The executor is taken by value, generic over AsExecutor — which is implemented on the natural sqlx reference types (&PgPool, &mut PgConnection, &mut Transaction, &mut PoolConnection, &mut T of each). Callers pass &pool, &mut conn, or &mut tx directly: queries::get_author(&pool, 1).await?; queries::get_author(&mut conn, 1).await?; queries::get_author(&mut tx, 1).await?; The Queries struct, its new/into_inner methods, and the impl Queries collector block are removed. The AsExecutor trait and all its impls are kept. Updates the four example crates and eight e2e expect.rs cases to the new shape, regenerates examples/*/src/queries.rs via mise run generate, and rewrites the README. Adds a design spec and implementation plan under docs/superpowers/. BREAKING CHANGE: callers using Queries::new(executor).foo(...) must switch to queries::foo(executor, ...). --- README.md | 19 +-- examples/advanced-types/src/main.rs | 5 +- examples/advanced-types/src/queries.rs | 64 ++++---- examples/basic/src/main.rs | 26 ++-- examples/basic/src/queries.rs | 138 ++++++++---------- examples/batch/src/main.rs | 10 +- examples/batch/src/queries.rs | 112 ++++++-------- examples/enums/src/main.rs | 21 +-- examples/enums/src/queries.rs | 119 +++++++-------- sqlc.yaml | 2 +- src/codegen/batch.rs | 22 +-- src/codegen/copyfrom.rs | 22 ++- src/codegen/mod.rs | 82 ++++------- src/codegen/query.rs | 111 +++++++------- tests/codegen.rs | 68 +++++++-- tests/e2e/cases/batch_exec/expect.rs | 6 +- tests/e2e/cases/batch_many/expect.rs | 13 +- tests/e2e/cases/batch_one/expect.rs | 7 +- .../config_output_name/postgresql/expect.rs | 4 +- tests/e2e/cases/copyfrom/expect.rs | 13 +- tests/e2e/cases/dynamic_slice/expect.rs | 5 +- tests/e2e/cases/one_named_params/expect.rs | 13 +- .../cases/row_derives/postgresql/expect.rs | 5 +- .../codegen__batch_dynamic_slice_param.snap | 119 +++++++-------- tests/snapshots/codegen__batchexec.snap | 55 +++---- tests/snapshots/codegen__batchmany.snap | 74 ++++------ tests/snapshots/codegen__batchone.snap | 74 ++++------ tests/snapshots/codegen__composite_types.snap | 24 +-- tests/snapshots/codegen__copyfrom.snap | 79 ++++------ .../codegen__dynamic_slice_param.snap | 80 +++++----- tests/snapshots/codegen__embed.snap | 50 +++---- tests/snapshots/codegen__enum_types.snap | 56 +++---- tests/snapshots/codegen__exec.snap | 25 +--- tests/snapshots/codegen__execlastid.snap | 31 ++-- tests/snapshots/codegen__execresult.snap | 26 +--- tests/snapshots/codegen__execrows.snap | 31 ++-- tests/snapshots/codegen__many.snap | 34 ++--- ...codegen__multidimensional_array_types.snap | 38 ++--- tests/snapshots/codegen__named_params.snap | 44 ++---- .../codegen__nullable_named_param.snap | 42 ++---- tests/snapshots/codegen__one.snap | 39 ++--- tests/snapshots/codegen__range_types.snap | 42 ++---- tests/snapshots/codegen__slice_param.snap | 42 ++---- 43 files changed, 776 insertions(+), 1116 deletions(-) diff --git a/README.md b/README.md index 51e7a89..e65f476 100644 --- a/README.md +++ b/README.md @@ -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` 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` wraps anything implementing the generated `AsExecutor` trait. `AsExecutor` is implemented for `PgPool`, `&PgPool`, `PgConnection`, `Transaction<'_, Postgres>`, `PoolConnection`, 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`, 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?; ``` @@ -132,7 +129,7 @@ Array types (`type[]`) become `Vec`. Nullable columns become `Option`. | `:batchmany` | `impl Stream, sqlx::Error>>` | Lazily fetch all rows per item | | `:copyfrom` | `Result` | Chunked bulk insert from any `IntoIterator` | -All functions are `&mut self` methods on `Queries`. The bound is `E: AsExecutor`, where `AsExecutor` is the trait emitted in each generated file. Impls cover `PgPool`, `&PgPool`, `PgConnection`, `Transaction<'_, Postgres>`, `PoolConnection`, 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`, 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`. diff --git a/examples/advanced-types/src/main.rs b/examples/advanced-types/src/main.rs index edb6253..70470c4 100644 --- a/examples/advanced-types/src/main.rs +++ b/examples/advanced-types/src/main.rs @@ -2,8 +2,6 @@ #[cfg(test)] mod queries; #[cfg(test)] -use queries::Queries; -#[cfg(test)] use sqlx::{Connection as _, PgConnection}; #[cfg(test)] @@ -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"); } diff --git a/examples/advanced-types/src/queries.rs b/examples/advanced-types/src/queries.rs index ce9be80..036bd1d 100644 --- a/examples/advanced-types/src/queries.rs +++ b/examples/advanced-types/src/queries.rs @@ -1,4 +1,4 @@ -// 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( @@ -6,22 +6,6 @@ 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>, -} -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>, -} pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -55,28 +39,30 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - 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>, } -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } +pub async fn get_event(mut db: E, id: i64) -> Result { + sqlx::query_as::<_, GetEventRow>(GET_EVENT) + .bind(id) + .fetch_one(db.as_executor()) + .await } -impl Queries { - pub async fn get_event(&mut self, id: i64) -> Result { - sqlx::query_as::<_, GetEventRow>(GET_EVENT) - .bind(id) - .fetch_one(self.db.as_executor()) - .await - } - pub async fn list_events(&mut self) -> Result, 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>, +} +pub async fn list_events(mut db: E) -> Result, sqlx::Error> { + sqlx::query_as::<_, ListEventsRow>(LIST_EVENTS) + .fetch_all(db.as_executor()) + .await } diff --git a/examples/basic/src/main.rs b/examples/basic/src/main.rs index cdea5a5..005425f 100644 --- a/examples/basic/src/main.rs +++ b/examples/basic/src/main.rs @@ -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] @@ -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); } diff --git a/examples/basic/src/queries.rs b/examples/basic/src/queries.rs index 9801f06..d31ddc2 100644 --- a/examples/basic/src/queries.rs +++ b/examples/basic/src/queries.rs @@ -1,4 +1,4 @@ -// 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( @@ -6,35 +6,6 @@ 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, -} -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, -} -#[derive(Debug, Clone)] -pub struct CreateAuthorParams { - pub name: String, - pub bio: Option, -} -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, -} -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>; } @@ -68,52 +39,67 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - 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, } -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } +pub async fn get_author(mut db: E, id: i64) -> Result { + sqlx::query_as::<_, GetAuthorRow>(GET_AUTHOR) + .bind(id) + .fetch_one(db.as_executor()) + .await } -impl Queries { - pub async fn get_author(&mut self, id: i64) -> Result { - sqlx::query_as::<_, GetAuthorRow>(GET_AUTHOR) - .bind(id) - .fetch_one(self.db.as_executor()) - .await - } - pub async fn list_authors(&mut self) -> Result, 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 { - 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 { - 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, +} +pub async fn list_authors(mut db: E) -> Result, 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, +} +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, +} +pub async fn create_author( + mut db: E, + arg: CreateAuthorParams, +) -> Result { + 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(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(mut db: E, id: i64) -> Result { + let result = sqlx::query(DELETE_AUTHOR_ROWS) + .bind(id) + .execute(db.as_executor()) + .await?; + Ok(result.rows_affected()) } diff --git a/examples/batch/src/main.rs b/examples/batch/src/main.rs index d608ef2..df85322 100644 --- a/examples/batch/src/main.rs +++ b/examples/batch/src/main.rs @@ -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)] @@ -34,10 +32,7 @@ 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"); @@ -45,8 +40,7 @@ async fn test_batch_roundtrip() { 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"); diff --git a/examples/batch/src/queries.rs b/examples/batch/src/queries.rs index d4d8c94..fa9625e 100644 --- a/examples/batch/src/queries.rs +++ b/examples/batch/src/queries.rs @@ -1,4 +1,4 @@ -// 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( @@ -6,14 +6,6 @@ reason = "generated queries may expose items a caller does not use" )] -const BATCH_GET_AUTHOR: &str = "SELECT id, name, bio FROM authors WHERE id = $1"; -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct BatchGetAuthorRow { - pub id: i64, - pub name: String, - pub bio: Option, -} -const BATCH_DELETE_AUTHOR: &str = "DELETE FROM authors WHERE id = $1"; pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -47,63 +39,51 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - db: E, +const BATCH_GET_AUTHOR: &str = "SELECT id, name, bio FROM authors WHERE id = $1"; +#[derive(Debug, Clone, sqlx::FromRow)] +pub struct BatchGetAuthorRow { + pub id: i64, + pub name: String, + pub bio: Option, } -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } +pub fn batch_get_author<'a, E, I>( + db: E, + items: I, +) -> impl futures_core::stream::Stream> + 'a +where + E: AsExecutor + 'a, + I: IntoIterator + 'a, + I::IntoIter: 'a, +{ + futures_util::stream::try_unfold((db, items.into_iter()), |(mut db, mut items)| async move { + let Some(item) = items.next() else { + return Ok(None); + }; + let row = sqlx::query_as::<_, BatchGetAuthorRow>(BATCH_GET_AUTHOR) + .bind(item) + .fetch_one(db.as_executor()) + .await?; + Ok(Some((row, (db, items)))) + }) } -impl Queries { - pub fn batch_get_author<'a, I>( - &'a mut self, - items: I, - ) -> impl futures_core::stream::Stream> + 'a - where - I: IntoIterator + 'a, - I::IntoIter: 'a, - E: 'a, - { - futures_util::stream::try_unfold( - (&mut self.db, items.into_iter()), - |(db, mut items)| async move { - let Some(item) = items.next() else { - return Ok(None); - }; - let row = sqlx::query_as::<_, BatchGetAuthorRow>(BATCH_GET_AUTHOR) - .bind(item) - .fetch_one(db.as_executor()) - .await?; - Ok(Some((row, (db, items)))) - }, - ) - } - pub fn batch_delete_author<'a, I>( - &'a mut self, - items: I, - ) -> impl futures_core::stream::Stream> + 'a - where - I: IntoIterator + 'a, - I::IntoIter: 'a, - E: 'a, - { - futures_util::stream::try_unfold( - (&mut self.db, items.into_iter()), - |(db, mut items)| async move { - let Some(item) = items.next() else { - return Ok(None); - }; - sqlx::query(BATCH_DELETE_AUTHOR) - .bind(item) - .execute(db.as_executor()) - .await?; - Ok(Some(((), (db, items)))) - }, - ) - } +const BATCH_DELETE_AUTHOR: &str = "DELETE FROM authors WHERE id = $1"; +pub fn batch_delete_author<'a, E, I>( + db: E, + items: I, +) -> impl futures_core::stream::Stream> + 'a +where + E: AsExecutor + 'a, + I: IntoIterator + 'a, + I::IntoIter: 'a, +{ + futures_util::stream::try_unfold((db, items.into_iter()), |(mut db, mut items)| async move { + let Some(item) = items.next() else { + return Ok(None); + }; + sqlx::query(BATCH_DELETE_AUTHOR) + .bind(item) + .execute(db.as_executor()) + .await?; + Ok(Some(((), (db, items)))) + }) } diff --git a/examples/enums/src/main.rs b/examples/enums/src/main.rs index 65290c2..e165566 100644 --- a/examples/enums/src/main.rs +++ b/examples/enums/src/main.rs @@ -2,7 +2,7 @@ #[cfg(test)] mod queries; #[cfg(test)] -use queries::{CreateUserParams, Queries, Status}; +use queries::{CreateUserParams, Status}; #[cfg(test)] use sqlx::{Connection as _, PgConnection}; @@ -40,20 +40,23 @@ async fn test_enum_roundtrip() { .await .unwrap(); - let mut q = Queries::new(conn); - - q.create_user(CreateUserParams { - name: "Alice".to_string(), - status: Status::Active, - }) + queries::create_user( + &mut conn, + CreateUserParams { + name: "Alice".to_string(), + status: Status::Active, + }, + ) .await .expect("create"); - let user = q.get_user(1).await.expect("get"); + let user = queries::get_user(&mut conn, 1).await.expect("get"); assert_eq!(user.name, "Alice"); assert_eq!(user.status, Status::Active); - let active = q.list_users_by_status(Status::Active).await.expect("list"); + let active = queries::list_users_by_status(&mut conn, Status::Active) + .await + .expect("list"); assert_eq!(active.len(), 1); } diff --git a/examples/enums/src/queries.rs b/examples/enums/src/queries.rs index 00d9cf9..35be388 100644 --- a/examples/enums/src/queries.rs +++ b/examples/enums/src/queries.rs @@ -1,4 +1,4 @@ -// 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( @@ -6,36 +6,6 @@ reason = "generated queries may expose items a caller does not use" )] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, sqlx::Type)] -#[sqlx(type_name = "status")] -pub enum Status { - #[sqlx(rename = "active")] - Active, - #[sqlx(rename = "inactive")] - Inactive, - #[sqlx(rename = "pending")] - Pending, -} -const GET_USER: &str = "SELECT id, name, status FROM users WHERE id = $1"; -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct GetUserRow { - pub id: i64, - pub name: String, - pub status: Status, -} -const LIST_USERS_BY_STATUS: &str = "SELECT id, name, status FROM users WHERE status = $1"; -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct ListUsersByStatusRow { - pub id: i64, - pub name: String, - pub status: Status, -} -#[derive(Debug, Clone)] -pub struct CreateUserParams { - pub name: String, - pub status: Status, -} -const CREATE_USER: &str = "INSERT INTO users (name, status) VALUES ($1, $2)"; pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -69,40 +39,59 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - db: E, +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, sqlx::Type)] +#[sqlx(type_name = "status")] +pub enum Status { + #[sqlx(rename = "active")] + Active, + #[sqlx(rename = "inactive")] + Inactive, + #[sqlx(rename = "pending")] + Pending, } -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } +const GET_USER: &str = "SELECT id, name, status FROM users WHERE id = $1"; +#[derive(Debug, Clone, sqlx::FromRow)] +pub struct GetUserRow { + pub id: i64, + pub name: String, + pub status: Status, } -impl Queries { - pub async fn get_user(&mut self, id: i64) -> Result { - sqlx::query_as::<_, GetUserRow>(GET_USER) - .bind(id) - .fetch_one(self.db.as_executor()) - .await - } - pub async fn list_users_by_status( - &mut self, - status: Status, - ) -> Result, sqlx::Error> { - sqlx::query_as::<_, ListUsersByStatusRow>(LIST_USERS_BY_STATUS) - .bind(status) - .fetch_all(self.db.as_executor()) - .await - } - pub async fn create_user(&mut self, arg: CreateUserParams) -> Result<(), sqlx::Error> { - sqlx::query(CREATE_USER) - .bind(arg.name) - .bind(arg.status) - .execute(self.db.as_executor()) - .await?; - Ok(()) - } +pub async fn get_user(mut db: E, id: i64) -> Result { + sqlx::query_as::<_, GetUserRow>(GET_USER) + .bind(id) + .fetch_one(db.as_executor()) + .await +} +const LIST_USERS_BY_STATUS: &str = "SELECT id, name, status FROM users WHERE status = $1"; +#[derive(Debug, Clone, sqlx::FromRow)] +pub struct ListUsersByStatusRow { + pub id: i64, + pub name: String, + pub status: Status, +} +pub async fn list_users_by_status( + mut db: E, + status: Status, +) -> Result, sqlx::Error> { + sqlx::query_as::<_, ListUsersByStatusRow>(LIST_USERS_BY_STATUS) + .bind(status) + .fetch_all(db.as_executor()) + .await +} +#[derive(Debug, Clone)] +pub struct CreateUserParams { + pub name: String, + pub status: Status, +} +const CREATE_USER: &str = "INSERT INTO users (name, status) VALUES ($1, $2)"; +pub async fn create_user( + mut db: E, + arg: CreateUserParams, +) -> Result<(), sqlx::Error> { + sqlx::query(CREATE_USER) + .bind(arg.name) + .bind(arg.status) + .execute(db.as_executor()) + .await?; + Ok(()) } diff --git a/sqlc.yaml b/sqlc.yaml index bfc6a40..44e9123 100644 --- a/sqlc.yaml +++ b/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: sqlc-gen-sqlx wasm: url: file://target/wasm32-wasip1/debug/sqlc-gen-sqlx.wasm - sha256: "837a7ff89040ce7bded3d87abaaa83591353eadc99c4cd46ed9572a450b09fd2" + sha256: "87780a1e4ebe3f7bfb3fdd4041e8f7127b5c35d540c40b456558d87f1ef62e5b" sql: - schema: examples/basic/schema.sql diff --git a/src/codegen/batch.rs b/src/codegen/batch.rs index 5ed2ea9..430069d 100644 --- a/src/codegen/batch.rs +++ b/src/codegen/batch.rs @@ -47,18 +47,18 @@ fn batch_stream_method( next_body: TokenStream, ) -> TokenStream { quote! { - pub fn #fn_name<'a, I>( - &'a mut self, + pub fn #fn_name<'a, E, I>( + db: E, items: I, ) -> impl futures_core::stream::Stream> + 'a where + E: AsExecutor + 'a, I: IntoIterator + 'a, I::IntoIter: 'a, - E: 'a, { futures_util::stream::try_unfold( - (&mut self.db, items.into_iter()), - |(db, mut items)| async move { + (db, items.into_iter()), + |(mut db, mut items)| async move { let Some(item) = items.next() else { return Ok(None); }; @@ -76,7 +76,7 @@ pub fn gen_batchexec( type_map: &TypeMap, config: &Config, col_overrides: &std::collections::HashMap, -) -> Result<(TokenStream, TokenStream), Error> { +) -> Result { let params = resolve_params(query.params.iter(), type_map, col_overrides)?; if params.is_empty() { return Err(Error::Codegen(format!( @@ -124,7 +124,7 @@ pub fn gen_batchexec( }; let method = batch_stream_method(&fn_name, &items_ty, "e! { () }, next_body); - Ok((quote! { #params_struct #const_tokens }, method)) + Ok(quote! { #params_struct #const_tokens #method }) } /// `:batchone` → `fn foo(items) -> impl Stream>` @@ -133,7 +133,7 @@ pub fn gen_batchone( type_map: &TypeMap, config: &Config, col_overrides: &std::collections::HashMap, -) -> Result<(TokenStream, TokenStream), Error> { +) -> Result { let params = resolve_params(query.params.iter(), type_map, col_overrides)?; if params.is_empty() { return Err(Error::Codegen(format!( @@ -184,7 +184,7 @@ pub fn gen_batchone( }; let method = batch_stream_method(&fn_name, &items_ty, "e! { #row_name }, next_body); - Ok((quote! { #params_struct #const_tokens #row_tokens }, method)) + Ok(quote! { #params_struct #const_tokens #row_tokens #method }) } /// `:batchmany` → `fn foo(items) -> impl Stream, Error>>` @@ -193,7 +193,7 @@ pub fn gen_batchmany( type_map: &TypeMap, config: &Config, col_overrides: &std::collections::HashMap, -) -> Result<(TokenStream, TokenStream), Error> { +) -> Result { let params = resolve_params(query.params.iter(), type_map, col_overrides)?; if params.is_empty() { return Err(Error::Codegen(format!( @@ -244,5 +244,5 @@ pub fn gen_batchmany( }; let method = batch_stream_method(&fn_name, &items_ty, "e! { Vec<#row_name> }, next_body); - Ok((quote! { #params_struct #const_tokens #row_tokens }, method)) + Ok(quote! { #params_struct #const_tokens #row_tokens #method }) } diff --git a/src/codegen/copyfrom.rs b/src/codegen/copyfrom.rs index d87c0ca..cc52e99 100644 --- a/src/codegen/copyfrom.rs +++ b/src/codegen/copyfrom.rs @@ -17,7 +17,7 @@ pub fn gen_copyfrom( type_map: &TypeMap, config: &Config, col_overrides: &std::collections::HashMap, -) -> Result<(TokenStream, TokenStream), Error> { +) -> Result { let params = resolve_params(query.params.iter(), type_map, col_overrides)?; if params.is_empty() { return Err(Error::Codegen(format!( @@ -49,8 +49,8 @@ pub fn gen_copyfrom( (None, quote! { #ty }, push_bind_calls(¶ms, None)) }; - let inner = quote! { - pub async fn #fn_name(&mut self, items: I) -> Result + let fn_tokens = quote! { + pub async fn #fn_name(mut db: E, items: I) -> Result where I: IntoIterator, { @@ -68,21 +68,19 @@ pub fn gen_copyfrom( #builder_binds }); - rows_affected += query_builder.build().execute(self.db.as_executor()).await?.rows_affected(); + rows_affected += query_builder.build().execute(db.as_executor()).await?.rows_affected(); } Ok(rows_affected) } }; - Ok(( - quote! { - #params_struct - #const_tokens - const #batch_size_name: usize = #batch_size; - }, - inner, - )) + Ok(quote! { + #params_struct + #const_tokens + const #batch_size_name: usize = #batch_size; + #fn_tokens + }) } fn insert_prefix(sql: &str) -> Result { diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index 05a2729..0ff98d2 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -15,42 +15,7 @@ pub fn generate(request: &GenerateRequestView<'_>, config: &Config) -> Result = Vec::new(); - let mut impl_fns: Vec = Vec::new(); - - for q in request.queries.iter() { - let (outer, inner) = match q.cmd { - ":exec" => query::gen_exec(q, &type_map, config, &col_overrides)?, - ":execrows" => query::gen_execrows(q, &type_map, config, &col_overrides)?, - ":execresult" => query::gen_execresult(q, &type_map, config, &col_overrides)?, - ":execlastid" => query::gen_execlastid(q, &type_map, config, &col_overrides)?, - ":batchexec" => batch::gen_batchexec(q, &type_map, config, &col_overrides)?, - ":batchone" => batch::gen_batchone(q, &type_map, config, &col_overrides)?, - ":batchmany" => batch::gen_batchmany(q, &type_map, config, &col_overrides)?, - ":copyfrom" => copyfrom::gen_copyfrom(q, &type_map, config, &col_overrides)?, - ":one" => query::gen_one(q, &type_map, config, &col_overrides)?, - ":many" => query::gen_many(q, &type_map, config, &col_overrides)?, - cmd => { - eprintln!("sqlc-gen-sqlx: skipping unsupported annotation {cmd}"); - continue; - } - }; - module_items.push(outer); - impl_fns.push(inner); - } - - for item in module_items { - emitter.push(item); - } - + // Emit the AsExecutor trait + impls up front so query functions can reference it. emitter.push(quote::quote! { pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; @@ -91,29 +56,34 @@ pub fn generate(request: &GenerateRequestView<'_>, config: &Config) -> Result { - db: E, - } - - impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - - pub fn into_inner(self) -> E { - self.db - } - } }); - if !impl_fns.is_empty() { - emitter.push(quote::quote! { - impl Queries { - #(#impl_fns)* + // Emit type definitions before query code. + for info in &catalog_info.enums { + emitter.push(enums::gen_enum(info, &config.enum_derives)?); + } + for info in &catalog_info.composites { + emitter.push(composites::gen_composite(info, &config.composite_derives)?); + } + + for q in request.queries.iter() { + let tokens = match q.cmd { + ":exec" => query::gen_exec(q, &type_map, config, &col_overrides)?, + ":execrows" => query::gen_execrows(q, &type_map, config, &col_overrides)?, + ":execresult" => query::gen_execresult(q, &type_map, config, &col_overrides)?, + ":execlastid" => query::gen_execlastid(q, &type_map, config, &col_overrides)?, + ":batchexec" => batch::gen_batchexec(q, &type_map, config, &col_overrides)?, + ":batchone" => batch::gen_batchone(q, &type_map, config, &col_overrides)?, + ":batchmany" => batch::gen_batchmany(q, &type_map, config, &col_overrides)?, + ":copyfrom" => copyfrom::gen_copyfrom(q, &type_map, config, &col_overrides)?, + ":one" => query::gen_one(q, &type_map, config, &col_overrides)?, + ":many" => query::gen_many(q, &type_map, config, &col_overrides)?, + cmd => { + eprintln!("sqlc-gen-sqlx: skipping unsupported annotation {cmd}"); + continue; } - }); + }; + emitter.push(tokens); } emitter.finish() diff --git a/src/codegen/query.rs b/src/codegen/query.rs index ca98e49..3d95ecb 100644 --- a/src/codegen/query.rs +++ b/src/codegen/query.rs @@ -435,13 +435,13 @@ pub(crate) fn build_fn_params( } } -/// `:one` → `async fn foo(exec, [params]) -> Result` +/// `:one` → `async fn foo(mut db: E, [params]) -> Result` pub fn gen_one( query: &QueryView<'_>, type_map: &TypeMap, config: &Config, col_overrides: &std::collections::HashMap, -) -> Result<(TokenStream, TokenStream), Error> { +) -> Result { let params = resolve_params(query.params.iter(), type_map, col_overrides)?; let columns = resolve_columns(query.columns.iter(), type_map, col_overrides)?; @@ -457,44 +457,43 @@ pub fn gen_one( let binds = bind_calls(¶ms, arg_ident.as_ref()); let dynamic_slice = has_dynamic_slice(query.text, ¶ms); - let inner_fn = if dynamic_slice { + let fn_tokens = if dynamic_slice { let sql_setup = dynamic_sql_setup(&const_name, ¶ms, arg_ident.as_ref()); let bind_setup = dynamic_bind_statements(¶ms, arg_ident.as_ref()); quote! { - pub async fn #fn_name(&mut self, #fn_params) -> Result<#row_name, sqlx::Error> { + pub async fn #fn_name(mut db: E, #fn_params) -> Result<#row_name, sqlx::Error> { #sql_setup let mut query = sqlx::query_as::<_, #row_name>(&sql); #bind_setup - query.fetch_one(self.db.as_executor()).await + query.fetch_one(db.as_executor()).await } } } else { quote! { - pub async fn #fn_name(&mut self, #fn_params) -> Result<#row_name, sqlx::Error> { + pub async fn #fn_name(mut db: E, #fn_params) -> Result<#row_name, sqlx::Error> { sqlx::query_as::<_, #row_name>(#const_name) #binds - .fetch_one(self.db.as_executor()) + .fetch_one(db.as_executor()) .await } } }; - let outer = quote! { + Ok(quote! { #params_struct #const_tokens #row_tokens - }; - - Ok((outer, inner_fn)) + #fn_tokens + }) } -/// `:many` → `async fn foo(exec, [params]) -> Result, sqlx::Error>` +/// `:many` → `async fn foo(mut db: E, [params]) -> Result, sqlx::Error>` pub fn gen_many( query: &QueryView<'_>, type_map: &TypeMap, config: &Config, col_overrides: &std::collections::HashMap, -) -> Result<(TokenStream, TokenStream), Error> { +) -> Result { let params = resolve_params(query.params.iter(), type_map, col_overrides)?; let columns = resolve_columns(query.columns.iter(), type_map, col_overrides)?; @@ -508,44 +507,43 @@ pub fn gen_many( let binds = bind_calls(¶ms, arg_ident.as_ref()); let dynamic_slice = has_dynamic_slice(query.text, ¶ms); - let inner_fn = if dynamic_slice { + let fn_tokens = if dynamic_slice { let sql_setup = dynamic_sql_setup(&const_name, ¶ms, arg_ident.as_ref()); let bind_setup = dynamic_bind_statements(¶ms, arg_ident.as_ref()); quote! { - pub async fn #fn_name(&mut self, #fn_params) -> Result, sqlx::Error> { + pub async fn #fn_name(mut db: E, #fn_params) -> Result, sqlx::Error> { #sql_setup let mut query = sqlx::query_as::<_, #row_name>(&sql); #bind_setup - query.fetch_all(self.db.as_executor()).await + query.fetch_all(db.as_executor()).await } } } else { quote! { - pub async fn #fn_name(&mut self, #fn_params) -> Result, sqlx::Error> { + pub async fn #fn_name(mut db: E, #fn_params) -> Result, sqlx::Error> { sqlx::query_as::<_, #row_name>(#const_name) #binds - .fetch_all(self.db.as_executor()) + .fetch_all(db.as_executor()) .await } } }; - let outer = quote! { + Ok(quote! { #params_struct #const_tokens #row_tokens - }; - - Ok((outer, inner_fn)) + #fn_tokens + }) } -/// `:execrows` → `async fn foo(exec, [params]) -> Result` +/// `:execrows` → `async fn foo(mut db: E, [params]) -> Result` pub fn gen_execrows( query: &QueryView<'_>, type_map: &TypeMap, config: &Config, col_overrides: &std::collections::HashMap, -) -> Result<(TokenStream, TokenStream), Error> { +) -> Result { let params = resolve_params(query.params.iter(), type_map, col_overrides)?; let fn_name = format_ident!("{}", to_snake_case(query.name)); let (const_tokens, const_name) = sql_const(query.name, query.text); @@ -553,39 +551,39 @@ pub fn gen_execrows( build_fn_params(query.name, ¶ms, &config.row_derives)?; let binds = bind_calls(¶ms, arg_ident.as_ref()); let dynamic_slice = has_dynamic_slice(query.text, ¶ms); - let inner = if dynamic_slice { + let fn_tokens = if dynamic_slice { let sql_setup = dynamic_sql_setup(&const_name, ¶ms, arg_ident.as_ref()); let bind_setup = dynamic_bind_statements(¶ms, arg_ident.as_ref()); quote! { - pub async fn #fn_name(&mut self, #fn_params) -> Result { + pub async fn #fn_name(mut db: E, #fn_params) -> Result { #sql_setup let mut query = sqlx::query(&sql); #bind_setup - let result = query.execute(self.db.as_executor()).await?; + let result = query.execute(db.as_executor()).await?; Ok(result.rows_affected()) } } } else { quote! { - pub async fn #fn_name(&mut self, #fn_params) -> Result { + pub async fn #fn_name(mut db: E, #fn_params) -> Result { let result = sqlx::query(#const_name) #binds - .execute(self.db.as_executor()) + .execute(db.as_executor()) .await?; Ok(result.rows_affected()) } } }; - Ok((quote! { #params_struct #const_tokens }, inner)) + Ok(quote! { #params_struct #const_tokens #fn_tokens }) } -/// `:execresult` → `async fn foo(exec, [params]) -> Result` +/// `:execresult` → `async fn foo(mut db: E, [params]) -> Result` pub fn gen_execresult( query: &QueryView<'_>, type_map: &TypeMap, config: &Config, col_overrides: &std::collections::HashMap, -) -> Result<(TokenStream, TokenStream), Error> { +) -> Result { let params = resolve_params(query.params.iter(), type_map, col_overrides)?; let fn_name = format_ident!("{}", to_snake_case(query.name)); let (const_tokens, const_name) = sql_const(query.name, query.text); @@ -593,37 +591,37 @@ pub fn gen_execresult( build_fn_params(query.name, ¶ms, &config.row_derives)?; let binds = bind_calls(¶ms, arg_ident.as_ref()); let dynamic_slice = has_dynamic_slice(query.text, ¶ms); - let inner = if dynamic_slice { + let fn_tokens = if dynamic_slice { let sql_setup = dynamic_sql_setup(&const_name, ¶ms, arg_ident.as_ref()); let bind_setup = dynamic_bind_statements(¶ms, arg_ident.as_ref()); quote! { - pub async fn #fn_name(&mut self, #fn_params) -> Result { + pub async fn #fn_name(mut db: E, #fn_params) -> Result { #sql_setup let mut query = sqlx::query(&sql); #bind_setup - query.execute(self.db.as_executor()).await + query.execute(db.as_executor()).await } } } else { quote! { - pub async fn #fn_name(&mut self, #fn_params) -> Result { + pub async fn #fn_name(mut db: E, #fn_params) -> Result { sqlx::query(#const_name) #binds - .execute(self.db.as_executor()) + .execute(db.as_executor()) .await } } }; - Ok((quote! { #params_struct #const_tokens }, inner)) + Ok(quote! { #params_struct #const_tokens #fn_tokens }) } -/// `:exec` → `async fn foo(exec, [params]) -> Result<(), sqlx::Error>` +/// `:exec` → `async fn foo(mut db: E, [params]) -> Result<(), sqlx::Error>` pub fn gen_exec( query: &QueryView<'_>, type_map: &TypeMap, config: &Config, col_overrides: &std::collections::HashMap, -) -> Result<(TokenStream, TokenStream), Error> { +) -> Result { let params = resolve_params(query.params.iter(), type_map, col_overrides)?; let fn_name = format_ident!("{}", to_snake_case(query.name)); let sql = query.text; @@ -635,46 +633,45 @@ pub fn gen_exec( let binds = bind_calls(¶ms, arg_ident.as_ref()); let dynamic_slice = has_dynamic_slice(query.text, ¶ms); - let inner_fn = if dynamic_slice { + let fn_tokens = if dynamic_slice { let sql_setup = dynamic_sql_setup(&const_name, ¶ms, arg_ident.as_ref()); let bind_setup = dynamic_bind_statements(¶ms, arg_ident.as_ref()); quote! { - pub async fn #fn_name(&mut self, #fn_params) -> Result<(), sqlx::Error> { + pub async fn #fn_name(mut db: E, #fn_params) -> Result<(), sqlx::Error> { #sql_setup let mut query = sqlx::query(&sql); #bind_setup - query.execute(self.db.as_executor()).await?; + query.execute(db.as_executor()).await?; Ok(()) } } } else { quote! { - pub async fn #fn_name(&mut self, #fn_params) -> Result<(), sqlx::Error> { + pub async fn #fn_name(mut db: E, #fn_params) -> Result<(), sqlx::Error> { sqlx::query(#const_name) #binds - .execute(self.db.as_executor()) + .execute(db.as_executor()) .await?; Ok(()) } } }; - let outer = quote! { + Ok(quote! { #params_struct #const_tokens - }; - - Ok((outer, inner_fn)) + #fn_tokens + }) } -/// `:execlastid` → `async fn foo(exec, [params]) -> Result` +/// `:execlastid` → `async fn foo(mut db: E, [params]) -> Result` /// where T is the type of the single RETURNING column. pub fn gen_execlastid( query: &QueryView<'_>, type_map: &TypeMap, config: &Config, col_overrides: &std::collections::HashMap, -) -> Result<(TokenStream, TokenStream), Error> { +) -> Result { let params = resolve_params(query.params.iter(), type_map, col_overrides)?; let fn_name = format_ident!("{}", to_snake_case(query.name)); let (const_tokens, const_name) = sql_const(query.name, query.text); @@ -696,28 +693,28 @@ pub fn gen_execlastid( )) })?; - let inner = if dynamic_slice { + let fn_tokens = if dynamic_slice { let sql_setup = dynamic_sql_setup(&const_name, ¶ms, arg_ident.as_ref()); let bind_setup = dynamic_bind_statements(¶ms, arg_ident.as_ref()); quote! { - pub async fn #fn_name(&mut self, #fn_params) -> Result<#ret_ty, sqlx::Error> { + pub async fn #fn_name(mut db: E, #fn_params) -> Result<#ret_ty, sqlx::Error> { #sql_setup let mut query = sqlx::query_as(&sql); #bind_setup - let (_row,): (#ret_ty,) = query.fetch_one(self.db.as_executor()).await?; + let (_row,): (#ret_ty,) = query.fetch_one(db.as_executor()).await?; Ok(_row) } } } else { quote! { - pub async fn #fn_name(&mut self, #fn_params) -> Result<#ret_ty, sqlx::Error> { + pub async fn #fn_name(mut db: E, #fn_params) -> Result<#ret_ty, sqlx::Error> { let (_row,): (#ret_ty,) = sqlx::query_as(#const_name) #binds - .fetch_one(self.db.as_executor()) + .fetch_one(db.as_executor()) .await?; Ok(_row) } } }; - Ok((quote! { #params_struct #const_tokens }, inner)) + Ok(quote! { #params_struct #const_tokens #fn_tokens }) } diff --git a/tests/codegen.rs b/tests/codegen.rs index b671d62..c4df9ac 100644 --- a/tests/codegen.rs +++ b/tests/codegen.rs @@ -96,8 +96,16 @@ fn snapshot_one() { "expected dead_code allow in:\n{code}" ); assert!( - code.contains("Queries"), - "expected Queries in:\n{code}" + code.contains("pub async fn get_author"), + "expected free function get_author in:\n{code}" + ); + assert!( + code.contains("mut db: E"), + "expected mut db: E parameter in:\n{code}" + ); + assert!( + !code.contains("Queries"), + "Queries wrapper should be gone in:\n{code}" ); } @@ -120,8 +128,12 @@ fn snapshot_many() { let code = String::from_utf8(resp.files[0].contents.clone()).unwrap(); assert_codegen_snapshot("many", &code); assert!( - code.contains("Queries"), - "expected Queries in:\n{code}" + code.contains("pub async fn list_authors"), + "expected free function list_authors in:\n{code}" + ); + assert!( + !code.contains("Queries"), + "Queries wrapper should be gone in:\n{code}" ); } @@ -146,8 +158,12 @@ fn snapshot_exec() { assert_codegen_snapshot("exec", &code); assert!( - code.contains("Queries"), - "expected Queries in:\n{code}" + code.contains("pub async fn delete_author"), + "expected free function delete_author in:\n{code}" + ); + assert!( + !code.contains("Queries"), + "Queries wrapper should be gone in:\n{code}" ); } @@ -170,8 +186,12 @@ fn snapshot_execrows() { let code = String::from_utf8(resp.files[0].contents.clone()).unwrap(); assert_codegen_snapshot("execrows", &code); assert!( - code.contains("Queries"), - "expected Queries in:\n{code}" + code.contains("pub async fn delete_author_rows"), + "expected free function delete_author_rows in:\n{code}" + ); + assert!( + !code.contains("Queries"), + "Queries wrapper should be gone in:\n{code}" ); } @@ -194,8 +214,12 @@ fn snapshot_execresult() { let code = String::from_utf8(resp.files[0].contents.clone()).unwrap(); assert_codegen_snapshot("execresult", &code); assert!( - code.contains("Queries"), - "expected Queries in:\n{code}" + code.contains("pub async fn delete_author_result"), + "expected free function delete_author_result in:\n{code}" + ); + assert!( + !code.contains("Queries"), + "Queries wrapper should be gone in:\n{code}" ); } @@ -279,8 +303,8 @@ fn snapshot_enum_types() { "expected Active variant in:\n{code}" ); assert!( - code.contains("Queries"), - "expected Queries in:\n{code}" + !code.contains("Queries"), + "Queries wrapper should be gone in:\n{code}" ); } @@ -629,6 +653,14 @@ fn snapshot_batchexec() { code.contains("futures_util::stream::try_unfold"), "expected stream implementation in:\n{code}" ); + assert!( + code.contains("pub fn batch_delete_author<'a, E, I>"), + "expected free function batch_delete_author<'a, E, I> in:\n{code}" + ); + assert!( + code.contains("db: E,"), + "expected db: E parameter in:\n{code}" + ); } #[test] @@ -662,6 +694,10 @@ fn snapshot_batchone() { code.contains("futures_util::stream::try_unfold"), "expected stream implementation in:\n{code}" ); + assert!( + code.contains("pub fn batch_get_author<'a, E, I>"), + "expected free function batch_get_author<'a, E, I> in:\n{code}" + ); } #[test] @@ -695,6 +731,10 @@ fn snapshot_batchmany() { code.contains("futures_util::stream::try_unfold"), "expected stream implementation in:\n{code}" ); + assert!( + code.contains("pub fn batch_list_authors<'a, E, I>"), + "expected free function batch_list_authors<'a, E, I> in:\n{code}" + ); } #[test] @@ -843,6 +883,10 @@ fn snapshot_copyfrom() { code.contains("COPY_AUTHORS_BATCH_SIZE"), "expected generated copyfrom batch size in:\n{code}" ); + assert!( + code.contains("pub async fn copy_authors"), + "expected free function copy_authors in:\n{code}" + ); } #[test] diff --git a/tests/e2e/cases/batch_exec/expect.rs b/tests/e2e/cases/batch_exec/expect.rs index 4fdfd46..9428f7e 100644 --- a/tests/e2e/cases/batch_exec/expect.rs +++ b/tests/e2e/cases/batch_exec/expect.rs @@ -4,8 +4,6 @@ use sqlx::{Connection as _, PgConnection}; #[path = "../src/queries.rs"] mod queries; -use queries::Queries; - #[tokio::test] async fn batchexec_streams_one_result_per_input() -> Result<(), Box> { let db_url = std::env::var("DATABASE_URL")?; @@ -34,9 +32,7 @@ async fn batchexec_streams_one_result_per_input() -> Result<(), Box>() .await?; diff --git a/tests/e2e/cases/batch_many/expect.rs b/tests/e2e/cases/batch_many/expect.rs index 626999e..46b82ca 100644 --- a/tests/e2e/cases/batch_many/expect.rs +++ b/tests/e2e/cases/batch_many/expect.rs @@ -4,8 +4,6 @@ use sqlx::{Connection as _, PgConnection}; #[path = "../src/queries.rs"] mod queries; -use queries::Queries; - #[tokio::test] async fn batchmany_streams_row_groups_per_input() -> Result<(), Box> { let db_url = std::env::var("DATABASE_URL")?; @@ -34,11 +32,12 @@ async fn batchmany_streams_row_groups_per_input() -> Result<(), Box>() - .await?; + let batches = queries::batch_list_authors_by_bio( + &mut conn, + [Some("%Rust%".to_string()), Some("%SQL%".to_string())], + ) + .try_collect::>() + .await?; assert_eq!(batches.len(), 2); assert_eq!(batches[0].len(), 2); diff --git a/tests/e2e/cases/batch_one/expect.rs b/tests/e2e/cases/batch_one/expect.rs index 143c189..1499562 100644 --- a/tests/e2e/cases/batch_one/expect.rs +++ b/tests/e2e/cases/batch_one/expect.rs @@ -4,8 +4,6 @@ use sqlx::{Connection as _, PgConnection}; #[path = "../src/queries.rs"] mod queries; -use queries::Queries; - #[tokio::test] async fn batchone_streams_one_row_per_input() -> Result<(), Box> { let db_url = std::env::var("DATABASE_URL")?; @@ -34,8 +32,9 @@ async fn batchone_streams_one_row_per_input() -> Result<(), Box>().await?; + let authors = queries::batch_get_author(&mut conn, [1_i64, 2, 3]) + .try_collect::>() + .await?; let names = authors .iter() .map(|author| author.name.as_str()) diff --git a/tests/e2e/cases/config_output_name/postgresql/expect.rs b/tests/e2e/cases/config_output_name/postgresql/expect.rs index 11b651b..08192f9 100644 --- a/tests/e2e/cases/config_output_name/postgresql/expect.rs +++ b/tests/e2e/cases/config_output_name/postgresql/expect.rs @@ -3,7 +3,6 @@ use sqlx::{Connection as _, PgConnection}; #[path = "../src/db.rs"] mod queries; -use queries::Queries; #[tokio::test] async fn custom_output_filename_is_respected() -> Result<(), Box> { @@ -32,8 +31,7 @@ async fn custom_output_filename_is_respected() -> Result<(), Box Result<(), Box> { @@ -24,9 +24,9 @@ async fn copyfrom_inserts_every_item() -> Result<(), Box> .execute(&mut conn) .await?; - let mut q = Queries::new(conn); - let inserted = q - .copy_authors([ + let inserted = queries::copy_authors( + &mut conn, + [ CopyAuthorsParams { name: "Bob".to_string(), bio: Some("Rustacean".to_string()), @@ -35,8 +35,9 @@ async fn copyfrom_inserts_every_item() -> Result<(), Box> name: "Cara".to_string(), bio: Some("SQL fan".to_string()), }, - ]) - .await?; + ], + ) + .await?; assert_eq!(inserted, 2); diff --git a/tests/e2e/cases/dynamic_slice/expect.rs b/tests/e2e/cases/dynamic_slice/expect.rs index af10719..edf80a7 100644 --- a/tests/e2e/cases/dynamic_slice/expect.rs +++ b/tests/e2e/cases/dynamic_slice/expect.rs @@ -3,8 +3,6 @@ use sqlx::{Connection as _, PgConnection}; #[path = "../src/queries.rs"] mod queries; -use queries::Queries; - #[tokio::test] async fn list_authors_by_ids_expands_dynamic_slice() -> Result<(), Box> { let db_url = std::env::var("DATABASE_URL")?; @@ -33,8 +31,7 @@ async fn list_authors_by_ids_expands_dynamic_slice() -> Result<(), Box Result<(), Box> { @@ -24,13 +24,14 @@ async fn create_author_uses_named_params() -> Result<(), Box Result<(), Box> { @@ -28,8 +28,7 @@ async fn row_derives_are_emitted_from_config() -> Result<(), Box, -} pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -50,68 +43,58 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - db: E, -} -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } +const BATCH_LIST_AUTHORS_BY_DYNAMIC_IDS: &str = "SELECT id, name, bio FROM authors WHERE id IN ($1)"; +#[derive(Debug, Clone, sqlx::FromRow)] +pub struct BatchListAuthorsByDynamicIdsRow { + pub id: i64, + pub name: String, + pub bio: Option, } -impl Queries { - pub fn batch_list_authors_by_dynamic_ids<'a, I>( - &'a mut self, - items: I, - ) -> impl futures_core::stream::Stream< - Item = Result, sqlx::Error>, - > + 'a - where - I: IntoIterator> + 'a, - I::IntoIter: 'a, - E: 'a, - { - futures_util::stream::try_unfold( - (&mut self.db, items.into_iter()), - |(db, mut items)| async move { - let Some(item) = items.next() else { - return Ok(None); - }; - let ids = item; - let mut sql = BATCH_LIST_AUTHORS_BY_DYNAMIC_IDS.to_string(); - let next_placeholder = 1usize; - let placeholder_1 = next_placeholder; - let slice_len = (ids).len(); - let replacement = if slice_len == 0 { - "NULL".to_string() - } else { - (placeholder_1..(placeholder_1 + slice_len)) - .map(|n| format!("${}", n)) - .collect::>() - .join(", ") - }; - if sql.contains("/*SLICE:ids*/?") { - sql = sql.replace("/*SLICE:ids*/?", &replacement); +pub fn batch_list_authors_by_dynamic_ids<'a, E, I>( + db: E, + items: I, +) -> impl futures_core::stream::Stream< + Item = Result, sqlx::Error>, +> + 'a +where + E: AsExecutor + 'a, + I: IntoIterator> + 'a, + I::IntoIter: 'a, +{ + futures_util::stream::try_unfold( + (db, items.into_iter()), + |(mut db, mut items)| async move { + let Some(item) = items.next() else { + return Ok(None); + }; + let ids = item; + let mut sql = BATCH_LIST_AUTHORS_BY_DYNAMIC_IDS.to_string(); + let next_placeholder = 1usize; + let placeholder_1 = next_placeholder; + let slice_len = (ids).len(); + let replacement = if slice_len == 0 { + "NULL".to_string() + } else { + (placeholder_1..(placeholder_1 + slice_len)) + .map(|n| format!("${}", n)) + .collect::>() + .join(", ") + }; + if sql.contains("/*SLICE:ids*/?") { + sql = sql.replace("/*SLICE:ids*/?", &replacement); + } else { + if sql.contains("/*SLICE:ids*/$1") { + sql = sql.replace("/*SLICE:ids*/$1", &replacement); } else { - if sql.contains("/*SLICE:ids*/$1") { - sql = sql.replace("/*SLICE:ids*/$1", &replacement); - } else { - sql = sql.replace("$1", &replacement); - } + sql = sql.replace("$1", &replacement); } - let mut query = sqlx::query_as::< - _, - BatchListAuthorsByDynamicIdsRow, - >(&sql); - for value in ids { - query = query.bind(value); - } - let rows = query.fetch_all(db.as_executor()).await?; - Ok(Some((rows, (db, items)))) - }, - ) - } + } + let mut query = sqlx::query_as::<_, BatchListAuthorsByDynamicIdsRow>(&sql); + for value in ids { + query = query.bind(value); + } + let rows = query.fetch_all(db.as_executor()).await?; + Ok(Some((rows, (db, items)))) + }, + ) } diff --git a/tests/snapshots/codegen__batchexec.snap b/tests/snapshots/codegen__batchexec.snap index 5792c41..3eba01b 100644 --- a/tests/snapshots/codegen__batchexec.snap +++ b/tests/snapshots/codegen__batchexec.snap @@ -10,7 +10,6 @@ expression: code reason = "generated queries may expose items a caller does not use" )] -const BATCH_DELETE_AUTHOR: &str = "DELETE FROM authors WHERE id = $1"; pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -44,40 +43,24 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { +const BATCH_DELETE_AUTHOR: &str = "DELETE FROM authors WHERE id = $1"; +pub fn batch_delete_author<'a, E, I>( db: E, -} -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } -} -impl Queries { - pub fn batch_delete_author<'a, I>( - &'a mut self, - items: I, - ) -> impl futures_core::stream::Stream> + 'a - where - I: IntoIterator + 'a, - I::IntoIter: 'a, - E: 'a, - { - futures_util::stream::try_unfold( - (&mut self.db, items.into_iter()), - |(db, mut items)| async move { - let Some(item) = items.next() else { - return Ok(None); - }; - sqlx::query(BATCH_DELETE_AUTHOR) - .bind(item) - .execute(db.as_executor()) - .await?; - Ok(Some(((), (db, items)))) - }, - ) - } + items: I, +) -> impl futures_core::stream::Stream> + 'a +where + E: AsExecutor + 'a, + I: IntoIterator + 'a, + I::IntoIter: 'a, +{ + futures_util::stream::try_unfold( + (db, items.into_iter()), + |(mut db, mut items)| async move { + let Some(item) = items.next() else { + return Ok(None); + }; + sqlx::query(BATCH_DELETE_AUTHOR).bind(item).execute(db.as_executor()).await?; + Ok(Some(((), (db, items)))) + }, + ) } diff --git a/tests/snapshots/codegen__batchmany.snap b/tests/snapshots/codegen__batchmany.snap index 46c1810..a67f094 100644 --- a/tests/snapshots/codegen__batchmany.snap +++ b/tests/snapshots/codegen__batchmany.snap @@ -10,13 +10,6 @@ expression: code reason = "generated queries may expose items a caller does not use" )] -const BATCH_LIST_AUTHORS: &str = "SELECT id, name, bio FROM authors WHERE bio LIKE $1"; -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct BatchListAuthorsRow { - pub id: i64, - pub name: String, - pub bio: Option, -} pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -50,42 +43,35 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - db: E, -} -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } +const BATCH_LIST_AUTHORS: &str = "SELECT id, name, bio FROM authors WHERE bio LIKE $1"; +#[derive(Debug, Clone, sqlx::FromRow)] +pub struct BatchListAuthorsRow { + pub id: i64, + pub name: String, + pub bio: Option, } -impl Queries { - pub fn batch_list_authors<'a, I>( - &'a mut self, - items: I, - ) -> impl futures_core::stream::Stream< - Item = Result, sqlx::Error>, - > + 'a - where - I: IntoIterator> + 'a, - I::IntoIter: 'a, - E: 'a, - { - futures_util::stream::try_unfold( - (&mut self.db, items.into_iter()), - |(db, mut items)| async move { - let Some(item) = items.next() else { - return Ok(None); - }; - let rows = sqlx::query_as::<_, BatchListAuthorsRow>(BATCH_LIST_AUTHORS) - .bind(item) - .fetch_all(db.as_executor()) - .await?; - Ok(Some((rows, (db, items)))) - }, - ) - } +pub fn batch_list_authors<'a, E, I>( + db: E, + items: I, +) -> impl futures_core::stream::Stream< + Item = Result, sqlx::Error>, +> + 'a +where + E: AsExecutor + 'a, + I: IntoIterator> + 'a, + I::IntoIter: 'a, +{ + futures_util::stream::try_unfold( + (db, items.into_iter()), + |(mut db, mut items)| async move { + let Some(item) = items.next() else { + return Ok(None); + }; + let rows = sqlx::query_as::<_, BatchListAuthorsRow>(BATCH_LIST_AUTHORS) + .bind(item) + .fetch_all(db.as_executor()) + .await?; + Ok(Some((rows, (db, items)))) + }, + ) } diff --git a/tests/snapshots/codegen__batchone.snap b/tests/snapshots/codegen__batchone.snap index cf3af70..30aba52 100644 --- a/tests/snapshots/codegen__batchone.snap +++ b/tests/snapshots/codegen__batchone.snap @@ -10,13 +10,6 @@ expression: code reason = "generated queries may expose items a caller does not use" )] -const BATCH_GET_AUTHOR: &str = "SELECT id, name, bio FROM authors WHERE id = $1"; -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct BatchGetAuthorRow { - pub id: i64, - pub name: String, - pub bio: Option, -} pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -50,42 +43,35 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - db: E, -} -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } +const BATCH_GET_AUTHOR: &str = "SELECT id, name, bio FROM authors WHERE id = $1"; +#[derive(Debug, Clone, sqlx::FromRow)] +pub struct BatchGetAuthorRow { + pub id: i64, + pub name: String, + pub bio: Option, } -impl Queries { - pub fn batch_get_author<'a, I>( - &'a mut self, - items: I, - ) -> impl futures_core::stream::Stream< - Item = Result, - > + 'a - where - I: IntoIterator + 'a, - I::IntoIter: 'a, - E: 'a, - { - futures_util::stream::try_unfold( - (&mut self.db, items.into_iter()), - |(db, mut items)| async move { - let Some(item) = items.next() else { - return Ok(None); - }; - let row = sqlx::query_as::<_, BatchGetAuthorRow>(BATCH_GET_AUTHOR) - .bind(item) - .fetch_one(db.as_executor()) - .await?; - Ok(Some((row, (db, items)))) - }, - ) - } +pub fn batch_get_author<'a, E, I>( + db: E, + items: I, +) -> impl futures_core::stream::Stream< + Item = Result, +> + 'a +where + E: AsExecutor + 'a, + I: IntoIterator + 'a, + I::IntoIter: 'a, +{ + futures_util::stream::try_unfold( + (db, items.into_iter()), + |(mut db, mut items)| async move { + let Some(item) = items.next() else { + return Ok(None); + }; + let row = sqlx::query_as::<_, BatchGetAuthorRow>(BATCH_GET_AUTHOR) + .bind(item) + .fetch_one(db.as_executor()) + .await?; + Ok(Some((row, (db, items)))) + }, + ) } diff --git a/tests/snapshots/codegen__composite_types.snap b/tests/snapshots/codegen__composite_types.snap index 8a4fd90..3a85283 100644 --- a/tests/snapshots/codegen__composite_types.snap +++ b/tests/snapshots/codegen__composite_types.snap @@ -10,13 +10,6 @@ expression: code reason = "generated queries may expose items a caller does not use" )] -#[derive(Debug, Clone, sqlx::Type)] -#[sqlx(type_name = "address")] -pub struct Address { - pub street: Option, - pub city: Option, - pub zip: Option, -} pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -50,15 +43,10 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - db: E, -} -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } +#[derive(Debug, Clone, sqlx::Type)] +#[sqlx(type_name = "address")] +pub struct Address { + pub street: Option, + pub city: Option, + pub zip: Option, } diff --git a/tests/snapshots/codegen__copyfrom.snap b/tests/snapshots/codegen__copyfrom.snap index 3161898..c354aeb 100644 --- a/tests/snapshots/codegen__copyfrom.snap +++ b/tests/snapshots/codegen__copyfrom.snap @@ -10,13 +10,6 @@ expression: code reason = "generated queries may expose items a caller does not use" )] -#[derive(Debug, Clone)] -pub struct CopyAuthorsParams { - pub name: String, - pub bio: Option, -} -const COPY_AUTHORS: &str = "INSERT INTO authors (name, bio) "; -const COPY_AUTHORS_BATCH_SIZE: usize = 32767usize; pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -50,48 +43,38 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - db: E, -} -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } +#[derive(Debug, Clone)] +pub struct CopyAuthorsParams { + pub name: String, + pub bio: Option, } -impl Queries { - pub async fn copy_authors(&mut self, items: I) -> Result - where - I: IntoIterator, - { - let mut rows_affected = 0u64; - let mut items = items.into_iter(); - loop { - let chunk = items.by_ref().take(COPY_AUTHORS_BATCH_SIZE).collect::>(); - if chunk.is_empty() { - break; - } - let mut query_builder = sqlx::QueryBuilder::< - sqlx::Postgres, - >::new(COPY_AUTHORS); - query_builder - .push_values( - chunk, - |mut b, item| { - b.push_bind(item.name); - b.push_bind(item.bio); - }, - ); - rows_affected - += query_builder - .build() - .execute(self.db.as_executor()) - .await? - .rows_affected(); +const COPY_AUTHORS: &str = "INSERT INTO authors (name, bio) "; +const COPY_AUTHORS_BATCH_SIZE: usize = 32767usize; +pub async fn copy_authors( + mut db: E, + items: I, +) -> Result +where + I: IntoIterator, +{ + let mut rows_affected = 0u64; + let mut items = items.into_iter(); + loop { + let chunk = items.by_ref().take(COPY_AUTHORS_BATCH_SIZE).collect::>(); + if chunk.is_empty() { + break; } - Ok(rows_affected) + let mut query_builder = sqlx::QueryBuilder::::new(COPY_AUTHORS); + query_builder + .push_values( + chunk, + |mut b, item| { + b.push_bind(item.name); + b.push_bind(item.bio); + }, + ); + rows_affected + += query_builder.build().execute(db.as_executor()).await?.rows_affected(); } + Ok(rows_affected) } diff --git a/tests/snapshots/codegen__dynamic_slice_param.snap b/tests/snapshots/codegen__dynamic_slice_param.snap index 516ffc5..1fe123e 100644 --- a/tests/snapshots/codegen__dynamic_slice_param.snap +++ b/tests/snapshots/codegen__dynamic_slice_param.snap @@ -10,13 +10,6 @@ expression: code reason = "generated queries may expose items a caller does not use" )] -const LIST_AUTHORS_BY_DYNAMIC_IDS: &str = "SELECT id, name, bio FROM authors WHERE id IN ($1)"; -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct ListAuthorsByDynamicIdsRow { - pub id: i64, - pub name: String, - pub bio: Option, -} pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -50,48 +43,41 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - db: E, -} -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } +const LIST_AUTHORS_BY_DYNAMIC_IDS: &str = "SELECT id, name, bio FROM authors WHERE id IN ($1)"; +#[derive(Debug, Clone, sqlx::FromRow)] +pub struct ListAuthorsByDynamicIdsRow { + pub id: i64, + pub name: String, + pub bio: Option, } -impl Queries { - pub async fn list_authors_by_dynamic_ids( - &mut self, - ids: Vec, - ) -> Result, sqlx::Error> { - let mut sql = LIST_AUTHORS_BY_DYNAMIC_IDS.to_string(); - let next_placeholder = 1usize; - let placeholder_1 = next_placeholder; - let slice_len = (ids).len(); - let replacement = if slice_len == 0 { - "NULL".to_string() - } else { - (placeholder_1..(placeholder_1 + slice_len)) - .map(|n| format!("${}", n)) - .collect::>() - .join(", ") - }; - if sql.contains("/*SLICE:ids*/?") { - sql = sql.replace("/*SLICE:ids*/?", &replacement); +pub async fn list_authors_by_dynamic_ids( + mut db: E, + ids: Vec, +) -> Result, sqlx::Error> { + let mut sql = LIST_AUTHORS_BY_DYNAMIC_IDS.to_string(); + let next_placeholder = 1usize; + let placeholder_1 = next_placeholder; + let slice_len = (ids).len(); + let replacement = if slice_len == 0 { + "NULL".to_string() + } else { + (placeholder_1..(placeholder_1 + slice_len)) + .map(|n| format!("${}", n)) + .collect::>() + .join(", ") + }; + if sql.contains("/*SLICE:ids*/?") { + sql = sql.replace("/*SLICE:ids*/?", &replacement); + } else { + if sql.contains("/*SLICE:ids*/$1") { + sql = sql.replace("/*SLICE:ids*/$1", &replacement); } else { - if sql.contains("/*SLICE:ids*/$1") { - sql = sql.replace("/*SLICE:ids*/$1", &replacement); - } else { - sql = sql.replace("$1", &replacement); - } + sql = sql.replace("$1", &replacement); } - let mut query = sqlx::query_as::<_, ListAuthorsByDynamicIdsRow>(&sql); - for value in ids { - query = query.bind(value); - } - query.fetch_all(self.db.as_executor()).await } + let mut query = sqlx::query_as::<_, ListAuthorsByDynamicIdsRow>(&sql); + for value in ids { + query = query.bind(value); + } + query.fetch_all(db.as_executor()).await } diff --git a/tests/snapshots/codegen__embed.snap b/tests/snapshots/codegen__embed.snap index 39f703b..a688fef 100644 --- a/tests/snapshots/codegen__embed.snap +++ b/tests/snapshots/codegen__embed.snap @@ -10,18 +10,6 @@ expression: code reason = "generated queries may expose items a caller does not use" )] -const GET_AUTHOR_EMBED: &str = "SELECT a.id, a.name, a.bio FROM authors a WHERE a.id = $1"; -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct AuthorsEmbed { - pub id: i64, - pub name: String, - pub bio: Option, -} -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct GetAuthorEmbedRow { - #[sqlx(flatten)] - pub authors: AuthorsEmbed, -} pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -55,26 +43,24 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - db: E, +const GET_AUTHOR_EMBED: &str = "SELECT a.id, a.name, a.bio FROM authors a WHERE a.id = $1"; +#[derive(Debug, Clone, sqlx::FromRow)] +pub struct AuthorsEmbed { + pub id: i64, + pub name: String, + pub bio: Option, } -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } +#[derive(Debug, Clone, sqlx::FromRow)] +pub struct GetAuthorEmbedRow { + #[sqlx(flatten)] + pub authors: AuthorsEmbed, } -impl Queries { - pub async fn get_author_embed( - &mut self, - id: i64, - ) -> Result { - sqlx::query_as::<_, GetAuthorEmbedRow>(GET_AUTHOR_EMBED) - .bind(id) - .fetch_one(self.db.as_executor()) - .await - } +pub async fn get_author_embed( + mut db: E, + id: i64, +) -> Result { + sqlx::query_as::<_, GetAuthorEmbedRow>(GET_AUTHOR_EMBED) + .bind(id) + .fetch_one(db.as_executor()) + .await } diff --git a/tests/snapshots/codegen__enum_types.snap b/tests/snapshots/codegen__enum_types.snap index cc53d6b..ef0ed75 100644 --- a/tests/snapshots/codegen__enum_types.snap +++ b/tests/snapshots/codegen__enum_types.snap @@ -10,21 +10,6 @@ expression: code reason = "generated queries may expose items a caller does not use" )] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, sqlx::Type)] -#[sqlx(type_name = "status")] -pub enum Status { - #[sqlx(rename = "active")] - Active, - #[sqlx(rename = "inactive")] - Inactive, - #[sqlx(rename = "banned")] - Banned, -} -const GET_USER_STATUS: &str = "SELECT status FROM users WHERE id = $1"; -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct GetUserStatusRow { - pub status: Status, -} pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -58,26 +43,27 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - db: E, +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, sqlx::Type)] +#[sqlx(type_name = "status")] +pub enum Status { + #[sqlx(rename = "active")] + Active, + #[sqlx(rename = "inactive")] + Inactive, + #[sqlx(rename = "banned")] + Banned, } -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } +const GET_USER_STATUS: &str = "SELECT status FROM users WHERE id = $1"; +#[derive(Debug, Clone, sqlx::FromRow)] +pub struct GetUserStatusRow { + pub status: Status, } -impl Queries { - pub async fn get_user_status( - &mut self, - id: i64, - ) -> Result { - sqlx::query_as::<_, GetUserStatusRow>(GET_USER_STATUS) - .bind(id) - .fetch_one(self.db.as_executor()) - .await - } +pub async fn get_user_status( + mut db: E, + id: i64, +) -> Result { + sqlx::query_as::<_, GetUserStatusRow>(GET_USER_STATUS) + .bind(id) + .fetch_one(db.as_executor()) + .await } diff --git a/tests/snapshots/codegen__exec.snap b/tests/snapshots/codegen__exec.snap index dd7bc10..910a6ec 100644 --- a/tests/snapshots/codegen__exec.snap +++ b/tests/snapshots/codegen__exec.snap @@ -10,7 +10,6 @@ expression: code reason = "generated queries may expose items a caller does not use" )] -const DELETE_AUTHOR: &str = "DELETE FROM authors WHERE id = $1"; pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -44,21 +43,11 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - db: E, -} -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } -} -impl Queries { - 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(()) - } +const DELETE_AUTHOR: &str = "DELETE FROM authors WHERE id = $1"; +pub async fn delete_author( + mut db: E, + id: i64, +) -> Result<(), sqlx::Error> { + sqlx::query(DELETE_AUTHOR).bind(id).execute(db.as_executor()).await?; + Ok(()) } diff --git a/tests/snapshots/codegen__execlastid.snap b/tests/snapshots/codegen__execlastid.snap index 541a12d..c13e91c 100644 --- a/tests/snapshots/codegen__execlastid.snap +++ b/tests/snapshots/codegen__execlastid.snap @@ -10,7 +10,6 @@ expression: code reason = "generated queries may expose items a caller does not use" )] -const CREATE_AUTHOR: &str = "INSERT INTO authors (name) VALUES ($1) RETURNING id"; pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -44,24 +43,14 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - db: E, -} -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } -} -impl Queries { - pub async fn create_author(&mut self, name: String) -> Result { - let (_row,): (i64,) = sqlx::query_as(CREATE_AUTHOR) - .bind(name) - .fetch_one(self.db.as_executor()) - .await?; - Ok(_row) - } +const CREATE_AUTHOR: &str = "INSERT INTO authors (name) VALUES ($1) RETURNING id"; +pub async fn create_author( + mut db: E, + name: String, +) -> Result { + let (_row,): (i64,) = sqlx::query_as(CREATE_AUTHOR) + .bind(name) + .fetch_one(db.as_executor()) + .await?; + Ok(_row) } diff --git a/tests/snapshots/codegen__execresult.snap b/tests/snapshots/codegen__execresult.snap index 3f73217..49a4893 100644 --- a/tests/snapshots/codegen__execresult.snap +++ b/tests/snapshots/codegen__execresult.snap @@ -10,7 +10,6 @@ expression: code reason = "generated queries may expose items a caller does not use" )] -const DELETE_AUTHOR_RESULT: &str = "DELETE FROM authors WHERE id = $1"; pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -44,23 +43,10 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - db: E, -} -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } -} -impl Queries { - pub async fn delete_author_result( - &mut self, - id: i64, - ) -> Result { - sqlx::query(DELETE_AUTHOR_RESULT).bind(id).execute(self.db.as_executor()).await - } +const DELETE_AUTHOR_RESULT: &str = "DELETE FROM authors WHERE id = $1"; +pub async fn delete_author_result( + mut db: E, + id: i64, +) -> Result { + sqlx::query(DELETE_AUTHOR_RESULT).bind(id).execute(db.as_executor()).await } diff --git a/tests/snapshots/codegen__execrows.snap b/tests/snapshots/codegen__execrows.snap index f9885af..08f44a4 100644 --- a/tests/snapshots/codegen__execrows.snap +++ b/tests/snapshots/codegen__execrows.snap @@ -10,7 +10,6 @@ expression: code reason = "generated queries may expose items a caller does not use" )] -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>; } @@ -44,24 +43,14 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - db: E, -} -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } -} -impl Queries { - pub async fn delete_author_rows(&mut self, id: i64) -> Result { - let result = sqlx::query(DELETE_AUTHOR_ROWS) - .bind(id) - .execute(self.db.as_executor()) - .await?; - Ok(result.rows_affected()) - } +const DELETE_AUTHOR_ROWS: &str = "DELETE FROM authors WHERE id = $1"; +pub async fn delete_author_rows( + mut db: E, + id: i64, +) -> Result { + let result = sqlx::query(DELETE_AUTHOR_ROWS) + .bind(id) + .execute(db.as_executor()) + .await?; + Ok(result.rows_affected()) } diff --git a/tests/snapshots/codegen__many.snap b/tests/snapshots/codegen__many.snap index 80ad060..9b34dfe 100644 --- a/tests/snapshots/codegen__many.snap +++ b/tests/snapshots/codegen__many.snap @@ -10,13 +10,6 @@ expression: code reason = "generated queries may expose items a caller does not use" )] -const LIST_AUTHORS: &str = "SELECT id, name, bio FROM authors"; -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct ListAuthorsRow { - pub id: i64, - pub name: String, - pub bio: Option, -} pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -50,22 +43,15 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - db: E, -} -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } +const LIST_AUTHORS: &str = "SELECT id, name, bio FROM authors"; +#[derive(Debug, Clone, sqlx::FromRow)] +pub struct ListAuthorsRow { + pub id: i64, + pub name: String, + pub bio: Option, } -impl Queries { - pub async fn list_authors(&mut self) -> Result, sqlx::Error> { - sqlx::query_as::<_, ListAuthorsRow>(LIST_AUTHORS) - .fetch_all(self.db.as_executor()) - .await - } +pub async fn list_authors( + mut db: E, +) -> Result, sqlx::Error> { + sqlx::query_as::<_, ListAuthorsRow>(LIST_AUTHORS).fetch_all(db.as_executor()).await } diff --git a/tests/snapshots/codegen__multidimensional_array_types.snap b/tests/snapshots/codegen__multidimensional_array_types.snap index 9c949b8..14b6012 100644 --- a/tests/snapshots/codegen__multidimensional_array_types.snap +++ b/tests/snapshots/codegen__multidimensional_array_types.snap @@ -10,11 +10,6 @@ expression: code reason = "generated queries may expose items a caller does not use" )] -const GET_MATRIX: &str = "SELECT matrix FROM tensors WHERE id = $1"; -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct GetMatrixRow { - pub matrix: Vec>, -} pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -48,26 +43,17 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - db: E, -} -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } +const GET_MATRIX: &str = "SELECT matrix FROM tensors WHERE id = $1"; +#[derive(Debug, Clone, sqlx::FromRow)] +pub struct GetMatrixRow { + pub matrix: Vec>, } -impl Queries { - pub async fn get_matrix( - &mut self, - matrix: Vec>, - ) -> Result { - sqlx::query_as::<_, GetMatrixRow>(GET_MATRIX) - .bind(matrix) - .fetch_one(self.db.as_executor()) - .await - } +pub async fn get_matrix( + mut db: E, + matrix: Vec>, +) -> Result { + sqlx::query_as::<_, GetMatrixRow>(GET_MATRIX) + .bind(matrix) + .fetch_one(db.as_executor()) + .await } diff --git a/tests/snapshots/codegen__named_params.snap b/tests/snapshots/codegen__named_params.snap index a57c97c..87c4bda 100644 --- a/tests/snapshots/codegen__named_params.snap +++ b/tests/snapshots/codegen__named_params.snap @@ -10,12 +10,6 @@ expression: code reason = "generated queries may expose items a caller does not use" )] -#[derive(Debug, Clone)] -pub struct UpdateAuthorNamedParamsParams { - pub set_name: String, - pub author_id: i64, -} -const UPDATE_AUTHOR_NAMED_PARAMS: &str = "UPDATE authors SET name = $1 WHERE id = $2"; pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -49,28 +43,20 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - db: E, -} -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } +#[derive(Debug, Clone)] +pub struct UpdateAuthorNamedParamsParams { + pub set_name: String, + pub author_id: i64, } -impl Queries { - pub async fn update_author_named_params( - &mut self, - arg: UpdateAuthorNamedParamsParams, - ) -> Result<(), sqlx::Error> { - sqlx::query(UPDATE_AUTHOR_NAMED_PARAMS) - .bind(arg.set_name) - .bind(arg.author_id) - .execute(self.db.as_executor()) - .await?; - Ok(()) - } +const UPDATE_AUTHOR_NAMED_PARAMS: &str = "UPDATE authors SET name = $1 WHERE id = $2"; +pub async fn update_author_named_params( + mut db: E, + arg: UpdateAuthorNamedParamsParams, +) -> Result<(), sqlx::Error> { + sqlx::query(UPDATE_AUTHOR_NAMED_PARAMS) + .bind(arg.set_name) + .bind(arg.author_id) + .execute(db.as_executor()) + .await?; + Ok(()) } diff --git a/tests/snapshots/codegen__nullable_named_param.snap b/tests/snapshots/codegen__nullable_named_param.snap index 6f1fd1b..82f8f1c 100644 --- a/tests/snapshots/codegen__nullable_named_param.snap +++ b/tests/snapshots/codegen__nullable_named_param.snap @@ -10,13 +10,6 @@ expression: code reason = "generated queries may expose items a caller does not use" )] -const LIST_AUTHORS_BY_OPTIONAL_BIO: &str = "SELECT id, name, bio FROM authors WHERE bio = $1"; -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct ListAuthorsByOptionalBioRow { - pub id: i64, - pub name: String, - pub bio: Option, -} pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -50,26 +43,19 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - db: E, -} -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } +const LIST_AUTHORS_BY_OPTIONAL_BIO: &str = "SELECT id, name, bio FROM authors WHERE bio = $1"; +#[derive(Debug, Clone, sqlx::FromRow)] +pub struct ListAuthorsByOptionalBioRow { + pub id: i64, + pub name: String, + pub bio: Option, } -impl Queries { - pub async fn list_authors_by_optional_bio( - &mut self, - bio: Option, - ) -> Result, sqlx::Error> { - sqlx::query_as::<_, ListAuthorsByOptionalBioRow>(LIST_AUTHORS_BY_OPTIONAL_BIO) - .bind(bio) - .fetch_all(self.db.as_executor()) - .await - } +pub async fn list_authors_by_optional_bio( + mut db: E, + bio: Option, +) -> Result, sqlx::Error> { + sqlx::query_as::<_, ListAuthorsByOptionalBioRow>(LIST_AUTHORS_BY_OPTIONAL_BIO) + .bind(bio) + .fetch_all(db.as_executor()) + .await } diff --git a/tests/snapshots/codegen__one.snap b/tests/snapshots/codegen__one.snap index d268541..73e7425 100644 --- a/tests/snapshots/codegen__one.snap +++ b/tests/snapshots/codegen__one.snap @@ -10,13 +10,6 @@ expression: 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, -} pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -50,23 +43,19 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - db: E, -} -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } +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, } -impl Queries { - pub async fn get_author(&mut self, id: i64) -> Result { - sqlx::query_as::<_, GetAuthorRow>(GET_AUTHOR) - .bind(id) - .fetch_one(self.db.as_executor()) - .await - } +pub async fn get_author( + mut db: E, + id: i64, +) -> Result { + sqlx::query_as::<_, GetAuthorRow>(GET_AUTHOR) + .bind(id) + .fetch_one(db.as_executor()) + .await } diff --git a/tests/snapshots/codegen__range_types.snap b/tests/snapshots/codegen__range_types.snap index e5ad4c6..5182269 100644 --- a/tests/snapshots/codegen__range_types.snap +++ b/tests/snapshots/codegen__range_types.snap @@ -10,13 +10,6 @@ expression: code reason = "generated queries may expose items a caller does not use" )] -const GET_EVENT_WINDOW: &str = "SELECT id, window, flags FROM events WHERE id = $1"; -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct GetEventWindowRow { - pub id: i64, - pub window: sqlx::postgres::types::PgRange>, - pub flags: bit_vec::BitVec, -} pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -50,26 +43,19 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - db: E, -} -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } +const GET_EVENT_WINDOW: &str = "SELECT id, window, flags FROM events WHERE id = $1"; +#[derive(Debug, Clone, sqlx::FromRow)] +pub struct GetEventWindowRow { + pub id: i64, + pub window: sqlx::postgres::types::PgRange>, + pub flags: bit_vec::BitVec, } -impl Queries { - pub async fn get_event_window( - &mut self, - id: i64, - ) -> Result { - sqlx::query_as::<_, GetEventWindowRow>(GET_EVENT_WINDOW) - .bind(id) - .fetch_one(self.db.as_executor()) - .await - } +pub async fn get_event_window( + mut db: E, + id: i64, +) -> Result { + sqlx::query_as::<_, GetEventWindowRow>(GET_EVENT_WINDOW) + .bind(id) + .fetch_one(db.as_executor()) + .await } diff --git a/tests/snapshots/codegen__slice_param.snap b/tests/snapshots/codegen__slice_param.snap index a2be06a..1a93267 100644 --- a/tests/snapshots/codegen__slice_param.snap +++ b/tests/snapshots/codegen__slice_param.snap @@ -10,13 +10,6 @@ expression: code reason = "generated queries may expose items a caller does not use" )] -const LIST_AUTHORS_BY_IDS: &str = "SELECT id, name, bio FROM authors WHERE id = ANY($1)"; -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct ListAuthorsByIdsRow { - pub id: i64, - pub name: String, - pub bio: Option, -} pub trait AsExecutor { fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>; } @@ -50,26 +43,19 @@ impl AsExecutor for &mut T { (**self).as_executor() } } -#[derive(Copy, Clone)] -pub struct Queries { - db: E, -} -impl Queries { - pub fn new(db: E) -> Self { - Self { db } - } - pub fn into_inner(self) -> E { - self.db - } +const LIST_AUTHORS_BY_IDS: &str = "SELECT id, name, bio FROM authors WHERE id = ANY($1)"; +#[derive(Debug, Clone, sqlx::FromRow)] +pub struct ListAuthorsByIdsRow { + pub id: i64, + pub name: String, + pub bio: Option, } -impl Queries { - pub async fn list_authors_by_ids( - &mut self, - id: Vec, - ) -> Result, sqlx::Error> { - sqlx::query_as::<_, ListAuthorsByIdsRow>(LIST_AUTHORS_BY_IDS) - .bind(id) - .fetch_all(self.db.as_executor()) - .await - } +pub async fn list_authors_by_ids( + mut db: E, + id: Vec, +) -> Result, sqlx::Error> { + sqlx::query_as::<_, ListAuthorsByIdsRow>(LIST_AUTHORS_BY_IDS) + .bind(id) + .fetch_all(db.as_executor()) + .await }