Skip to content

feat(core): Massive update to Pagination + update Backend functions #168

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

Merged
merged 3 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ name = "chrono"
path = "./examples/chrono.rs"
required-features = ["all", "libsql"]

[[example]]
name = "pagination"
path = "./examples/pagination.rs"
required-features = ["all", "libsql", "pagination"]

[[example]]
name = "turso-libsql"
path = "./examples/turso-libsql/src/main.rs"
Expand Down
57 changes: 57 additions & 0 deletions examples/pagination.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//! # Pagination Example
//!
//! This example demonstrates how to use the `chrono` crate with `geekorm`.
use anyhow::Result;
use geekorm::{prelude::*, GEEKORM_BANNER, GEEKORM_VERSION};

#[derive(Table, Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
struct Projects {
#[geekorm(primary_key, auto_increment)]
pub id: PrimaryKey<i32>,
#[geekorm(unique)]
pub name: String,
}

#[tokio::main]
async fn main() -> Result<()> {
#[cfg(debug_assertions)]
env_logger::builder()
.filter_level(log::LevelFilter::Debug)
.init();

println!("{} v{}\n", GEEKORM_BANNER, GEEKORM_VERSION);
let connection = create_projects().await?;

let mut page = Projects::paginate(&connection).await?;

/// Get the first page of projects
let mut projects = page.next(&connection).await?;
assert_eq!(page.page(), 0);
for project in &projects {
println!("Project: {}", project.name);
}

projects = page.next(&connection).await?;
assert_eq!(projects.len(), 100);
assert_eq!(page.page(), 1);

Ok(())
}

// Helper function to create 1000 projects
async fn create_projects() -> Result<libsql::Connection> {
// Initialize an in-memory database
let db = libsql::Builder::new_local(":memory:").build().await?;
let connection = db.connect()?;
Projects::create_table(&connection).await?;

for pname in 1..=1000 {
let mut prj = Projects::new(format!("geekorm-{}", pname));
prj.save(&connection).await?;
}

let total = Projects::total(&connection).await?;
assert_eq!(total, 1000);

Ok(connection)
}
38 changes: 38 additions & 0 deletions geekorm-core/src/backends/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,40 @@ where
.await
}

/// Fetch all rows from the table
#[allow(async_fn_in_trait, unused_variables)]
async fn all(connection: &'a C) -> Result<Vec<Self>, crate::Error> {
C::query::<Self>(
connection,
Self::query_select().table(Self::table()).build()?,
)
.await
}

/// Fetch by Page
#[cfg(feature = "pagination")]
#[allow(async_fn_in_trait, unused_variables)]
async fn page(connection: &'a C, page: &crate::Page) -> Result<Vec<Self>, crate::Error> {
C::query::<Self>(
connection,
QueryBuilder::select()
.table(Self::table())
.page(page)
.build()?,
)
.await
}

/// Create a new Pagination instance with the current table and fetch
/// total number of rows
#[cfg(feature = "pagination")]
#[allow(async_fn_in_trait, unused_variables)]
async fn paginate(connection: &'a C) -> Result<crate::Pagination<Self>, crate::Error> {
let mut page = crate::Pagination::new();
page.set_total(Self::total(connection).await? as u32);
Ok(page)
}

/// Update the current object in the database
#[allow(async_fn_in_trait, unused_variables)]
async fn update(&mut self, connection: &'a C) -> Result<(), crate::Error> {
Expand All @@ -139,6 +173,10 @@ where
async fn fetch(&mut self, connection: &'a C) -> Result<(), crate::Error>;

/// Fetch all rows from the database
#[deprecated(
since = "0.8.4",
note = "Please use the `all` method instead of `fetch_all`"
)]
#[allow(async_fn_in_trait, unused_variables)]
async fn fetch_all(connection: &'a C) -> Result<Vec<Self>, crate::Error> {
C::query::<Self>(
Expand Down
5 changes: 5 additions & 0 deletions geekorm-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ pub enum Error {
#[error("No Rows Found in the database for the query")]
NoRowsFound,

/// Pagination Error
#[cfg(feature = "pagination")]
#[error("Pagination Error: {0}")]
PaginationError(String),

/// Not Implemented
#[error("Not Implemented")]
NotImplemented,
Expand Down
4 changes: 3 additions & 1 deletion geekorm-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ 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::pages::Page;
#[cfg(feature = "pagination")]
pub use crate::queries::pagination::Pagination;
pub use crate::queries::{Query, QueryBuilder};
#[cfg(feature = "two-factor-auth")]
pub use crate::utils::tfa::TwoFactorAuth;
Expand Down
4 changes: 2 additions & 2 deletions geekorm-core/src/queries/builder.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#[cfg(feature = "pagination")]
use super::pages::Pagination;
use super::pages::Page;
use crate::builder::{
joins::{TableJoin, TableJoinOptions, TableJoins},
models::{QueryCondition, QueryOrder, QueryType, WhereCondition},
Expand Down Expand Up @@ -324,7 +324,7 @@ impl QueryBuilder {

/// Add a page to the query
#[cfg(feature = "pagination")]
pub fn page(mut self, page: &Pagination) -> Self {
pub fn page(mut self, page: &Page) -> Self {
self.offset = Some(page.offset() as usize);
self.limit = Some(page.limit as usize);
self
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 @@ -4,6 +4,8 @@
pub mod builder;
#[cfg(feature = "pagination")]
pub mod pages;
#[cfg(feature = "pagination")]
pub mod pagination;
/// The Query Module
pub mod query;

Expand Down
100 changes: 79 additions & 21 deletions geekorm-core/src/queries/pages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
/// Default limit for max page size
const DEFAULT_LIMIT: u32 = 100;

/// Pagination struct
/// Page struct for pagination.
///
/// This is a simple struct to handle pagination for queries.
///
/// ```rust
/// # use geekorm::prelude::*;
Expand All @@ -17,9 +19,8 @@ const DEFAULT_LIMIT: u32 = 100;
/// }
///
/// # fn main() {
/// // Create a new Pagination instance
/// let mut page = Pagination::new();
/// # assert_eq!(page.page(), 0);
/// // Create a new Page instance
/// let mut page = Page::new();
/// # assert_eq!(page.limit(), 100);
/// # assert_eq!(page.offset(), 0);
///
Expand All @@ -45,33 +46,40 @@ const DEFAULT_LIMIT: u32 = 100;
/// # "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));
/// let page_max = Page::from((1, 10_000));
/// # assert_eq!(page_max.limit(), 100);
///
/// let option_page = Pagination::from((Some(5), Some(10)));
/// let option_page = Page::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 struct Page {
pub(crate) page: u32,
pub(crate) limit: u32,
pub(crate) total: u32,
}

impl Pagination {
/// Create a new Pagination instance
impl Page {
/// Create a new Page instance
pub fn new() -> Self {
Pagination {
Page {
page: 0,
limit: DEFAULT_LIMIT,
total: 0,
}
}
/// Update current page to the next page
pub fn next(&mut self) {
self.page += 1;
// Don't overflow the page number, reset to 0
if self.page == u32::MAX {
self.page = 0;
} else {
self.page += 1;
}
}
/// Update current page to the previous page
pub fn prev(&mut self) {
Expand All @@ -89,33 +97,81 @@ impl Pagination {
}
/// Offset for the query
pub fn offset(&self) -> u32 {
self.page * self.limit
if self.page == u32::MAX {
0
} else {
self.page * self.limit
}
}
/// Total number of pages
pub fn pages(&self) -> u32 {
if self.total == 0 {
0
} else {
(self.total as f64 / self.limit as f64).ceil() as u32
}
}
/// Set the total number of rows
pub fn set_total(&mut self, total: u32) {
self.total = total;
}

/// Get the maximum number of pages based on the total number of rows
pub fn max(&self) -> u32 {
if self.total == 0 {
0
} else {
(self.total as f64 / self.limit as f64).ceil() as u32
}
}
}

impl Default for Pagination {
impl Default for Page {
fn default() -> Self {
Pagination {
page: 0,
Page {
page: u32::MAX,
limit: DEFAULT_LIMIT,
total: 0,
}
}
}

impl From<(u32, u32)> for Pagination {
impl From<(u32, u32)> for Page {
fn from(p: (u32, u32)) -> Self {
let limit = if p.1 > DEFAULT_LIMIT {
DEFAULT_LIMIT
} else {
p.1
};
Pagination { page: p.0, limit }
Page {
page: p.0,
limit,
..Default::default()
}
}
}

impl From<(Option<u32>, Option<u32>)> for Pagination {
impl From<(Option<u32>, Option<u32>)> for Page {
fn from(value: (Option<u32>, Option<u32>)) -> Self {
let mut page = Pagination::new();
let mut page = Page::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
}
}

/// Implement From for Page (page, limit, total)
impl From<(Option<u32>, Option<u32>, u32)> for Page {
fn from(value: (Option<u32>, Option<u32>, u32)) -> Self {
let mut page = Page::new();
if let Some(p) = value.0 {
page.page = p;
}
Expand All @@ -126,15 +182,17 @@ impl From<(Option<u32>, Option<u32>)> for Pagination {
page.limit = l;
}
}
page.total = value.2;
page
}
}

impl From<u32> for Pagination {
impl From<u32> for Page {
fn from(value: u32) -> Self {
Pagination {
Page {
page: value,
limit: DEFAULT_LIMIT,
..Default::default()
}
}
}
Loading
Loading