Skip to content

Commit

Permalink
SQLxPagination requires a single connection instead of a complete pool (
Browse files Browse the repository at this point in the history
#35)

This pull request includes significant changes to the `page-hunter`
project, primarily focusing on modifying the `SQLxPagination` to require
a single connection instead of a connection pool. This update affects
multiple files and introduces breaking changes. Additionally, the
version has been updated to `0.5.0`.

### Breaking Changes:

* `README.md`, `page-hunter/src/lib.rs`: Updated examples to use
`PgConnection` instead of `PgPool`, and added instructions for acquiring
a single connection from a pool before running `paginate`.
[[1]](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L204-R205)
[[2]](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L216-R216)
[[3]](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L227-R233)
[[4]](diffhunk://#diff-98b36862e570e602d4e1243b2002ed62181ef1e476b652e63f282f1a700f59a9L184-R185)
[[5]](diffhunk://#diff-98b36862e570e602d4e1243b2002ed62181ef1e476b652e63f282f1a700f59a9L196-R196)
[[6]](diffhunk://#diff-98b36862e570e602d4e1243b2002ed62181ef1e476b652e63f282f1a700f59a9L207-R213)
*
[`page-hunter/CHANGELOG.md`](diffhunk://#diff-796b552e2e17042f9cdc871a10c457777b7e0cdcf342074efc6c4fd37bac24b6R8-R13):
Documented the breaking change that `SqlxPagination` now requires a
single connection instead of a connection pool.
[[1]](diffhunk://#diff-796b552e2e17042f9cdc871a10c457777b7e0cdcf342074efc6c4fd37bac24b6R8-R13)
[[2]](diffhunk://#diff-796b552e2e17042f9cdc871a10c457777b7e0cdcf342074efc6c4fd37bac24b6L18-R24)
[[3]](diffhunk://#diff-796b552e2e17042f9cdc871a10c457777b7e0cdcf342074efc6c4fd37bac24b6L29-R35)
*
[`page-hunter/Cargo.toml`](diffhunk://#diff-b66be18f3a8e4dacb33abd0fd4152b1ddd61f79ed96a368ac193112c1d525834L4-R4):
Updated the version to `0.5.0`.

### Codebase Updates:

* `examples/axum/src/db/repository/categories.rs`,
`examples/axum/src/db/repository/products.rs`: Modified to acquire a
single connection from the pool before running `paginate`.
[[1]](diffhunk://#diff-224cd2775e8c94344ba22aa730ebb2a0b407a1382252965f7b9eb338346b9962R76-R79)
[[2]](diffhunk://#diff-1b85346dad9f26c7812d76958fd4f36cfd9681c950b2c26eab2ba579269b56c6R127-R130)
*
[`page-hunter/src/pagination/sqlx/queries.rs`](diffhunk://#diff-cc5ef0715780e23c1d25b1c22fe34ab45f9d7b746fa36bbad1f0866632d75d1cR1-R5):
Refactored `get_total_rows`, `get_page_rows`, and `paginate_rows`
functions to use a SQL query string instead of `QueryBuilder`. Updated
`SQLxPagination` trait and its implementation accordingly.
[[1]](diffhunk://#diff-cc5ef0715780e23c1d25b1c22fe34ab45f9d7b746fa36bbad1f0866632d75d1cR1-R5)
[[2]](diffhunk://#diff-cc5ef0715780e23c1d25b1c22fe34ab45f9d7b746fa36bbad1f0866632d75d1cL13-R21)
[[3]](diffhunk://#diff-cc5ef0715780e23c1d25b1c22fe34ab45f9d7b746fa36bbad1f0866632d75d1cL28-R30)
[[4]](diffhunk://#diff-cc5ef0715780e23c1d25b1c22fe34ab45f9d7b746fa36bbad1f0866632d75d1cL42-R49)
[[5]](diffhunk://#diff-cc5ef0715780e23c1d25b1c22fe34ab45f9d7b746fa36bbad1f0866632d75d1cL59-R58)
[[6]](diffhunk://#diff-cc5ef0715780e23c1d25b1c22fe34ab45f9d7b746fa36bbad1f0866632d75d1cL93-R108)
[[7]](diffhunk://#diff-cc5ef0715780e23c1d25b1c22fe34ab45f9d7b746fa36bbad1f0866632d75d1cL126-L129)
[[8]](diffhunk://#diff-cc5ef0715780e23c1d25b1c22fe34ab45f9d7b746fa36bbad1f0866632d75d1cL140-R130)
[[9]](diffhunk://#diff-cc5ef0715780e23c1d25b1c22fe34ab45f9d7b746fa36bbad1f0866632d75d1cL150-R162)
*
[`page-hunter/src/pagination/sqlx/tests/test_pg.rs`](diffhunk://#diff-e7fff6a6fe680c789203aa0bea2dd87ddffda8bc9c8d2773885438b16ba4190dL7-R8):
Updated tests to use `PgConnection` and acquire a single connection from
the pool before running `paginate`.
[[1]](diffhunk://#diff-e7fff6a6fe680c789203aa0bea2dd87ddffda8bc9c8d2773885438b16ba4190dL7-R8)
[[2]](diffhunk://#diff-e7fff6a6fe680c789203aa0bea2dd87ddffda8bc9c8d2773885438b16ba4190dL35-R49)
[[3]](diffhunk://#diff-e7fff6a6fe680c789203aa0bea2dd87ddffda8bc9c8d2773885438b16ba4190dL102-R109)
[[4]](diffhunk://#diff-e7fff6a6fe680c789203aa0bea2dd87ddffda8bc9c8d2773885438b16ba4190dL150-R154)
  • Loading branch information
JMTamayo authored Jan 16, 2025
2 parents 097d0e2 + ffea574 commit bea6184
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 93 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,8 @@ Take a look at the [examples](https://github.com/JMTamayo/page-hunter/tree/main/
To paginate records from a Postgres database:
```rust,no_run
use page_hunter::{Page, SQLxPagination};
use sqlx::postgres::{PgPool, Postgres};
use sqlx::{FromRow, QueryBuilder};
use sqlx::postgres::{PgConnection, Postgres};
use sqlx::{Connection, FromRow, QueryBuilder};
use uuid::Uuid;
#[tokio::main]
Expand All @@ -213,7 +213,7 @@ To paginate records from a Postgres database:
name: String,
}
let pool: PgPool = PgPool::connect(
let mut conn: PgConnection = PgConnection::connect(
"postgres://username:password@localhost/db"
).await.unwrap_or_else(|error| {
panic!("Error connecting to database: {:?}", error);
Expand All @@ -224,13 +224,13 @@ To paginate records from a Postgres database:
);
let page: Page<Country> =
query.paginate(&pool, 0, 10).await.unwrap_or_else(|error| {
query.paginate(&mut conn, 0, 10).await.unwrap_or_else(|error| {
panic!("Error paginating records: {:?}", error);
});
}
```

Similar to using pagination for Postgres, `SQLxPagination` can be used for MySQL and SQLite.
Similar to using pagination for Postgres, `SQLxPagination` can be used for MySQL and SQLite. If you are working with a connection pool, you can [Acquire](https://docs.rs/sqlx/latest/sqlx/trait.Acquire.html) a single connection before running `paginate`.

## DEVELOPMENT
To test `page-hunter`, follow these recommendations:
Expand Down
4 changes: 3 additions & 1 deletion examples/axum/src/db/repository/categories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,10 @@ impl<'p> CategoriesRepositoryMethods for CategoriesRepository<'p> {
None => debug!("No name_like provided"),
}

let mut conn = self.get_pool().acquire().await?;

let categories: PaginatedCategories = query
.paginate(self.get_pool(), search_by.get_page(), search_by.get_size())
.paginate(&mut conn, search_by.get_page(), search_by.get_size())
.await?;

Ok(categories)
Expand Down
4 changes: 3 additions & 1 deletion examples/axum/src/db/repository/products.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,10 @@ impl<'p> ProductsRepositoryMethods for ProductsRepository<'p> {
None => debug!("No category_name_like provided"),
}

let mut conn = self.get_pool().acquire().await?;

let products_base: PaginatedProductsBase = query
.paginate(self.get_pool(), search_by.get_page(), search_by.get_size())
.paginate(&mut conn, search_by.get_page(), search_by.get_size())
.await?;

let products: PaginatedProducts = Page::new(
Expand Down
10 changes: 8 additions & 2 deletions page-hunter/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 🚀 v0.5.0 [Pending]

### Changed:

- 🔨 `SqlxPagination` requires a single connection and not a complete connection pool **[⚠️ BREAKING CHANGE]** by [@JMTamayo](https://github.com/JMTamayo).

## 🚀 v0.4.2 [2025-01-11]

### Changed:
Expand All @@ -15,7 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed:

- 🪚 Fix README.md posting on crates.io [@JMTamayo](https://github.com/JMTamayo).
- 🪚 Fix README.md posting on crates.io by [@JMTamayo](https://github.com/JMTamayo).

## 🚀 v0.4.0 [2025-01-11]

Expand All @@ -26,7 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed:

- 🔨 Include unit tests inside each module by [@JMTamayo](https://github.com/JMTamayo).
- 🔨 Implement `SqlxPagination` in a more general way that applies to PostgreSQL, MySQL, and SQLite. Unify ***pg-sqlx***, ***mysql-sqlx*** and ***sqlite-sqlx*** features into ***sqlx*** feature by [@JMTamayo](https://github.com/JMTamayo).
- 🔨 Implement `SqlxPagination` in a more general way that applies to PostgreSQL, MySQL, and SQLite. Unify ***pg-sqlx***, ***mysql-sqlx*** and ***sqlite-sqlx*** features into ***sqlx*** feature **[⚠️ BREAKING CHANGE]** by [@JMTamayo](https://github.com/JMTamayo).
- 🔨 Upgrade ***utoipa*** version to greater than or equal to **0.5.3** by [@JMTamayo](https://github.com/JMTamayo).
- 🔨 Upgrade ***serde*** version to greater than or equal to **1.0.210** by [@JMTamayo](https://github.com/JMTamayo).
- 🔨 Optimize the implementation of the `Debug`, `Clone`, `Serialize` and `ToSchema` traits by [@JMTamayo](https://github.com/JMTamayo).
Expand Down
2 changes: 1 addition & 1 deletion page-hunter/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "page-hunter"
description = "The pagination powerhouse, built with Rust"
version = "0.4.2"
version = "0.5.0"
authors = ["Juan Manuel Tamayo <[email protected]>"]
edition = "2021"
repository = "https://github.com/jmtamayo/page-hunter"
Expand Down
10 changes: 5 additions & 5 deletions page-hunter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,8 @@
//! To paginate records from a Postgres database:
//! ```rust,no_run
//! use page_hunter::{Page, SQLxPagination};
//! use sqlx::postgres::{PgPool, Postgres};
//! use sqlx::{FromRow, QueryBuilder};
//! use sqlx::postgres::{PgConnection, Postgres};
//! use sqlx::{Connection, FromRow, QueryBuilder};
//! use uuid::Uuid;
//!
//! #[tokio::main]
Expand All @@ -193,7 +193,7 @@
//! name: String,
//! }
//!
//! let pool: PgPool = PgPool::connect(
//! let mut conn: PgConnection = PgConnection::connect(
//! "postgres://username:password@localhost/db"
//! ).await.unwrap_or_else(|error| {
//! panic!("Error connecting to database: {:?}", error);
Expand All @@ -204,13 +204,13 @@
//! );
//!
//! let page: Page<Country> =
//! query.paginate(&pool, 0, 10).await.unwrap_or_else(|error| {
//! query.paginate(&mut conn, 0, 10).await.unwrap_or_else(|error| {
//! panic!("Error paginating records: {:?}", error);
//! });
//! }
//! ```
//!
//! Similar to using pagination for Postgres, [`SQLxPagination`] can be used for MySQL and SQLite.
//! Similar to using pagination for Postgres, [`SQLxPagination`] can be used for MySQL and SQLite. If you are working with a connection pool, you can [Acquire](https://docs.rs/sqlx/latest/sqlx/trait.Acquire.html) a single connection before running [paginate](`SQLxPagination::paginate`).
mod book;
mod errors;
mod page;
Expand Down
71 changes: 33 additions & 38 deletions page-hunter/src/pagination/sqlx/queries.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::future::Future;

use sqlx::{
query, query_scalar, Acquire, ColumnIndex, Database, Decode, Error as SqlxError, Executor,
FromRow, IntoArguments, QueryBuilder, Type,
query, query_scalar, ColumnIndex, Database, Decode, Error as SqlxError, Executor, FromRow,
IntoArguments, QueryBuilder, Type,
};

#[allow(unused_imports)]
Expand All @@ -10,13 +12,13 @@ use crate::{ErrorKind, Page, PaginationError, PaginationResult};
///
/// ### Arguments:
/// - **conn**: A mutable reference to a connection to the database.
/// - **query_builder**: A reference to a [`QueryBuilder`] instance.
/// - **query_str**: A reference to a SQL query string.
///
/// ### Returns:
/// The total number of records if successful, otherwise a [`PaginationError`] error.
async fn get_total_rows<'c, 'q, DB>(
conn: &'c mut DB::Connection,
query_builder: &QueryBuilder<'q, DB>,
query_str: &str,
) -> PaginationResult<usize>
where
DB: Database,
Expand All @@ -25,10 +27,7 @@ where
for<'a> DB::Arguments<'a>: IntoArguments<'a, DB>,
usize: ColumnIndex<<DB>::Row>,
{
let query_str: String = format!(
"SELECT count(*) from ({}) as temp_table;",
query_builder.sql(),
);
let query_str: String = format!("SELECT count(*) from ({}) as temp_table;", query_str,);

let total: usize = query_scalar::<DB, i64>(&query_str).fetch_one(conn).await? as usize;

Expand All @@ -39,15 +38,15 @@ where
///
/// ### Arguments:
/// - **conn**: A mutable reference to a connection to the database.
/// - **query_builder**: A reference to a [`QueryBuilder`] instance.
/// - ***query_str**: A reference to a SQL query string.
/// - **page**: The page index.
/// - **size**: The number of records per page.
///
/// ### Returns:
/// A [`PaginationResult`] containing a vector of records of type [`Database::Row`] if successful, otherwise a [`PaginationError`] error.
async fn get_page_rows<'c, 'q, DB>(
conn: &'c mut DB::Connection,
query_builder: &QueryBuilder<'q, DB>,
query_str: &str,
page: usize,
size: usize,
) -> PaginationResult<Vec<DB::Row>>
Expand All @@ -56,12 +55,7 @@ where
for<'e> &'e mut DB::Connection: Executor<'e, Database = DB>,
for<'a> DB::Arguments<'a>: IntoArguments<'a, DB>,
{
let query_str: String = format!(
"{} LIMIT {} OFFSET {};",
query_builder.sql(),
size,
size * page,
);
let query_str: String = format!("{} LIMIT {} OFFSET {};", query_str, size, size * page,);

let rows: Vec<DB::Row> = query::<DB>(&query_str).fetch_all(conn).await?;

Expand Down Expand Up @@ -89,32 +83,29 @@ where
/// Paginate results from a SQL query into a [`Page`] model.
///
/// ### Arguments:
/// - **conn**: A mutable reference to a connection to the database, which must implement the [`Acquire`] trait.
/// - **query_builder**: A reference to a [`QueryBuilder`] instance.
/// - **conn**: A mutable reference to a connection to the database.
/// - ***query_str**: A reference to a SQL query string.
/// - **page**: The page index.
/// - **size**: The number of records per page.
///
/// ### Returns:
/// A [`PaginationResult`] containing a [`Page`] model of the paginated records `S`, where `S` must implement the [`FromRow`] for given [`Database::Row`] type according to the database.
async fn paginate_rows<'q, DB, A, S>(
conn: A,
query_builder: &QueryBuilder<'q, DB>,
async fn paginate_rows<'c, 'q, DB, S>(
conn: &'c mut DB::Connection,
query_str: &str,
page: usize,
size: usize,
) -> PaginationResult<Page<S>>
where
DB: Database,
for<'a> A: Acquire<'a, Database = DB>,
for<'b> &'b mut DB::Connection: Executor<'b, Database = DB>,
for<'c> i64: Type<DB> + Decode<'c, DB>,
for<'d> DB::Arguments<'d>: IntoArguments<'d, DB>,
for<'e> &'e mut DB::Connection: Executor<'e, Database = DB>,
for<'t> i64: Type<DB> + Decode<'t, DB>,
for<'a> DB::Arguments<'a>: IntoArguments<'a, DB>,
usize: ColumnIndex<<DB>::Row>,
S: for<'r> FromRow<'r, DB::Row> + Clone,
{
let mut c = conn.acquire().await?;

let total: usize = get_total_rows(&mut c, query_builder).await?;
let rows: Vec<DB::Row> = get_page_rows(&mut c, query_builder, page, size).await?;
let total: usize = get_total_rows(conn, query_str).await?;
let rows: Vec<DB::Row> = get_page_rows(conn, query_str, page, size).await?;
let items: Vec<S> = parse_rows::<DB, S>(rows).await?;

let page: Page<S> = Page::new(&items, page, size, total)?;
Expand All @@ -123,10 +114,9 @@ where
}

/// Trait to paginate results from a SQL query into a [`Page`] model from database using [`sqlx`].
pub trait SQLxPagination<DB, A, S>
pub trait SQLxPagination<DB, S>
where
DB: Database,
for<'a> A: Acquire<'a, Database = DB>,
for<'b> &'b mut DB::Connection: Executor<'b, Database = DB>,
for<'c> i64: Type<DB> + Decode<'c, DB>,
for<'d> DB::Arguments<'d>: IntoArguments<'d, DB>,
Expand All @@ -137,7 +127,7 @@ where
/// Available for Postgres, MySQL or SQLite databases.
///
/// ### Arguments:
/// - **conn**: A mutable reference to a connection to the database, which must implement the [`Acquire`] trait.
/// - **conn**: A mutable reference to a connection to the database.
/// - **page**: The page index.
/// - **size**: The number of records per page.
///
Expand All @@ -147,23 +137,28 @@ where
/// Only available when the `sqlx` feature is enabled.
fn paginate(
&self,
conn: A,
conn: &mut DB::Connection,
page: usize,
size: usize,
) -> impl std::future::Future<Output = PaginationResult<Page<S>>>;
) -> impl Future<Output = PaginationResult<Page<S>>>;
}

impl<DB, A, S> SQLxPagination<DB, A, S> for QueryBuilder<'_, DB>
impl<DB, S> SQLxPagination<DB, S> for QueryBuilder<'_, DB>
where
DB: Database,
for<'a> A: Acquire<'a, Database = DB>,
for<'b> &'b mut DB::Connection: Executor<'b, Database = DB>,
for<'c> i64: Type<DB> + Decode<'c, DB>,
for<'d> DB::Arguments<'d>: IntoArguments<'d, DB>,
usize: ColumnIndex<<DB>::Row>,
S: for<'r> FromRow<'r, DB::Row> + Clone,
{
async fn paginate(&self, conn: A, page: usize, size: usize) -> PaginationResult<Page<S>> {
paginate_rows(conn, self, page, size).await
async fn paginate(
&self,
conn: &mut DB::Connection,
page: usize,
size: usize,
) -> PaginationResult<Page<S>> {
let query_str: &str = self.sql();
paginate_rows(conn, query_str, page, size).await
}
}
Loading

0 comments on commit bea6184

Please sign in to comment.