From ae17d5eca67fc85ec37d09b80748fcce31536052 Mon Sep 17 00:00:00 2001 From: GeekMasher Date: Mon, 25 Nov 2024 22:35:14 +0000 Subject: [PATCH 1/2] feat(core): Add pagination support --- Cargo.toml | 3 +- geekorm-core/Cargo.toml | 1 + geekorm-core/src/lib.rs | 2 + geekorm-core/src/queries/builder.rs | 10 ++ geekorm-core/src/queries/mod.rs | 2 + geekorm-core/src/queries/pages.rs | 140 ++++++++++++++++++++++++++++ src/lib.rs | 3 + 7 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 geekorm-core/src/queries/pages.rs diff --git a/Cargo.toml b/Cargo.toml index 4e18301..4c2addf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,11 +44,12 @@ tag-name = "v{{version}}" [features] default = ["all", "backends"] # All features (minus backends) -all = ["uuid", "chrono", "new", "helpers", "rand", "hash"] +all = ["uuid", "chrono", "new", "pagination", "helpers", "rand", "hash"] uuid = ["geekorm-core/uuid"] chrono = ["geekorm-derive/chrono", "geekorm-core/chrono"] semver = ["geekorm-derive/semver", "geekorm-core/semver"] +pagination = ["geekorm-core/pagination"] # Two Factor Authentication tfa = ["two-factor-auth", "two-factor-auth-qr"] two-factor-auth = ["geekorm-core/two-factor-auth"] diff --git a/geekorm-core/Cargo.toml b/geekorm-core/Cargo.toml index 9b2331b..6bb863f 100644 --- a/geekorm-core/Cargo.toml +++ b/geekorm-core/Cargo.toml @@ -26,6 +26,7 @@ default = [] uuid = ["dep:uuid"] chrono = ["dep:chrono"] semver = ["dep:semver"] +pagination = [] # TFA (Two Factor Authentication) tfa = ["two-factor-auth", "two-factor-auth-qr"] two-factor-auth = ["dep:totp-rs"] diff --git a/geekorm-core/src/lib.rs b/geekorm-core/src/lib.rs index 0bd4da8..f66a51f 100644 --- a/geekorm-core/src/lib.rs +++ b/geekorm-core/src/lib.rs @@ -23,6 +23,8 @@ pub use crate::builder::columntypes::{ColumnType, ColumnTypeOptions}; pub use crate::builder::keys::{ForeignKey, PrimaryKey}; pub use crate::builder::table::Table; pub use crate::builder::values::{Value, Values}; +#[cfg(feature = "pagination")] +pub use crate::queries::pages::Pagination; pub use crate::queries::{Query, QueryBuilder}; #[cfg(feature = "two-factor-auth")] pub use crate::utils::tfa::TwoFactorAuth; diff --git a/geekorm-core/src/queries/builder.rs b/geekorm-core/src/queries/builder.rs index 112113e..4f9e8b2 100644 --- a/geekorm-core/src/queries/builder.rs +++ b/geekorm-core/src/queries/builder.rs @@ -9,6 +9,8 @@ use crate::{ Error, Table, ToSqlite, }; +use super::pages::Pagination; + /// The QueryBuilder is how you can build dynamically queries using the builder pattern. /// /// # Features @@ -321,6 +323,14 @@ impl QueryBuilder { self } + /// Add a page to the query + #[cfg(feature = "pagination")] + pub fn page(mut self, page: &Pagination) -> Self { + self.offset = Some(page.offset() as usize); + self.limit = Some(page.limit as usize); + self + } + /// Build a Query from the QueryBuilder and perform some checks pub fn build(&self) -> Result { if let Some(ref error) = self.error { diff --git a/geekorm-core/src/queries/mod.rs b/geekorm-core/src/queries/mod.rs index 5a2fb6e..0dcd93d 100644 --- a/geekorm-core/src/queries/mod.rs +++ b/geekorm-core/src/queries/mod.rs @@ -2,6 +2,8 @@ /// The QueryBuilder Module pub mod builder; +#[cfg(feature = "pagination")] +pub mod pages; /// The Query Module pub mod query; diff --git a/geekorm-core/src/queries/pages.rs b/geekorm-core/src/queries/pages.rs new file mode 100644 index 0000000..5b6c7e0 --- /dev/null +++ b/geekorm-core/src/queries/pages.rs @@ -0,0 +1,140 @@ +//! # GeekORM Pages + +/// Default limit for max page size +const DEFAULT_LIMIT: u32 = 100; + +/// Pagination struct +/// +/// ```rust +/// # use geekorm::prelude::*; +/// +/// #[derive(Table, Debug, Default, Clone, serde::Serialize, serde::Deserialize)] +/// pub struct Users { +/// pub id: PrimaryKeyInteger, +/// pub username: String, +/// pub age: i32, +/// pub postcode: Option, +/// } +/// +/// # fn main() { +/// // Create a new Pagination instance +/// let mut page = Pagination::new(); +/// # assert_eq!(page.page(), 0); +/// # assert_eq!(page.limit(), 100); +/// # assert_eq!(page.offset(), 0); +/// +/// // Update the page to the next page +/// page.next(); +/// # assert_eq!(page.page(), 1); +/// # assert_eq!(page.limit(), 100); +/// # assert_eq!(page.offset(), 100); +/// +/// # page.next(); +/// # assert_eq!(page.offset(), 200); +/// # page.prev(); +/// +/// // Build a query to select rows from the table +/// let select_query = Users::query_select() +/// .where_eq("username", "geekmasher") +/// .page(&page) +/// .order_by("age", QueryOrder::Asc) +/// .build() +/// .expect("Failed to build select query"); +/// # assert_eq!( +/// # select_query.query, +/// # "SELECT id, username, age, postcode FROM Users WHERE username = ? ORDER BY age ASC LIMIT 100 OFFSET 100;" +/// # ); +/// +/// let page_max = Pagination::from((1, 10_000)); +/// # assert_eq!(page_max.limit(), 100); +/// +/// let option_page = Pagination::from((Some(5), Some(10))); +/// # assert_eq!(option_page.page(), 5); +/// # assert_eq!(option_page.limit(), 10); +/// # assert_eq!(option_page.offset(), 50); +/// +/// # } +/// ``` +#[derive(Debug)] +pub struct Pagination { + pub(crate) page: u32, + pub(crate) limit: u32, +} + +impl Pagination { + /// Create a new Pagination instance + pub fn new() -> Self { + Pagination { + page: 0, + limit: DEFAULT_LIMIT, + } + } + /// Update current page to the next page + pub fn next(&mut self) { + self.page += 1; + } + /// Update current page to the previous page + pub fn prev(&mut self) { + if self.page > 0 { + self.page -= 1; + } + } + /// Page number + pub fn page(&self) -> u32 { + self.page + } + /// Limit the rows accessed + pub fn limit(&self) -> u32 { + self.limit + } + /// Offset for the query + pub fn offset(&self) -> u32 { + self.page * self.limit + } +} + +impl Default for Pagination { + fn default() -> Self { + Pagination { + page: 0, + limit: DEFAULT_LIMIT, + } + } +} + +impl From<(u32, u32)> for Pagination { + fn from(p: (u32, u32)) -> Self { + let limit = if p.1 > DEFAULT_LIMIT { + DEFAULT_LIMIT + } else { + p.1 + }; + Pagination { page: p.0, limit } + } +} + +impl From<(Option, Option)> for Pagination { + fn from(value: (Option, Option)) -> Self { + let mut page = Pagination::new(); + if let Some(p) = value.0 { + page.page = p; + } + if let Some(l) = value.1 { + if l > DEFAULT_LIMIT { + page.limit = DEFAULT_LIMIT; + } else { + page.limit = l; + } + } + page + } +} + +impl From for Pagination { + fn from(value: u32) -> Self { + Pagination { + page: value, + limit: DEFAULT_LIMIT, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 2382d31..62946c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,6 +52,7 @@ pub const GEEKORM_BANNER: &str = r#" ______ __ ____ ____ __ ___ / /_/ / __/ __/ ,< / /_/ / _, _/ / / / \____/\___/\___/_/|_|\____/_/ |_/_/ /_/"#; +#[doc(hidden)] pub mod prelude { //! GeekORM prelude //! @@ -89,6 +90,8 @@ pub mod prelude { pub use geekorm_core::builder::columns::{Column, Columns}; pub use geekorm_core::builder::columntypes::{ColumnType, ColumnTypeOptions}; pub use geekorm_core::builder::table::Table as BuilderTable; + #[cfg(feature = "pagination")] + pub use geekorm_core::queries::pages::Pagination; // Keys Modules pub use geekorm_core::builder::keys::foreign::{ForeignKey, ForeignKeyInteger}; From f5632a5006f12532aca606c75bb3d24ff7110564 Mon Sep 17 00:00:00 2001 From: GeekMasher Date: Mon, 25 Nov 2024 22:37:57 +0000 Subject: [PATCH 2/2] fix: Update use statements --- geekorm-core/src/queries/builder.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/geekorm-core/src/queries/builder.rs b/geekorm-core/src/queries/builder.rs index 4f9e8b2..2de3699 100644 --- a/geekorm-core/src/queries/builder.rs +++ b/geekorm-core/src/queries/builder.rs @@ -1,16 +1,15 @@ +#[cfg(feature = "pagination")] +use super::pages::Pagination; use crate::builder::{ joins::{TableJoin, TableJoinOptions, TableJoins}, models::{QueryCondition, QueryOrder, QueryType, WhereCondition}, }; -use crate::queries::Query; - use crate::{ builder::values::{Value, Values}, + queries::Query, Error, Table, ToSqlite, }; -use super::pages::Pagination; - /// The QueryBuilder is how you can build dynamically queries using the builder pattern. /// /// # Features