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 }