Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): Add pagination support #150

Merged
merged 2 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
1 change: 1 addition & 0 deletions geekorm-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
2 changes: 2 additions & 0 deletions geekorm-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
13 changes: 11 additions & 2 deletions geekorm-core/src/queries/builder.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#[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,
};

Expand Down Expand Up @@ -321,6 +322,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<Query, crate::Error> {
if let Some(ref error) = self.error {
Expand Down
2 changes: 2 additions & 0 deletions geekorm-core/src/queries/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

/// The QueryBuilder Module
pub mod builder;
#[cfg(feature = "pagination")]
pub mod pages;
/// The Query Module
pub mod query;

Expand Down
140 changes: 140 additions & 0 deletions geekorm-core/src/queries/pages.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
/// }
///
/// # 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<u32>, Option<u32>)> for Pagination {
fn from(value: (Option<u32>, Option<u32>)) -> 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<u32> for Pagination {
fn from(value: u32) -> Self {
Pagination {
page: value,
limit: DEFAULT_LIMIT,
}
}
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub const GEEKORM_BANNER: &str = r#" ______ __ ____ ____ __ ___
/ /_/ / __/ __/ ,< / /_/ / _, _/ / / /
\____/\___/\___/_/|_|\____/_/ |_/_/ /_/"#;

#[doc(hidden)]
pub mod prelude {
//! GeekORM prelude
//!
Expand Down Expand Up @@ -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};
Expand Down