1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-31 09:24:31 +03:00

storage: simplify pagination

This commit is contained in:
Quentin Gliech
2023-01-13 18:25:25 +01:00
parent 195203823a
commit 1344527934
7 changed files with 90 additions and 99 deletions

View File

@ -22,7 +22,7 @@ use url::Url;
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
pagination::{process_page, Page, QueryBuilderExt}, pagination::{Page, QueryBuilderExt},
tracing::ExecuteExt, tracing::ExecuteExt,
Clock, DatabaseError, DatabaseInconsistencyError, LookupResultExt, Clock, DatabaseError, DatabaseInconsistencyError, LookupResultExt,
}; };
@ -379,19 +379,13 @@ impl<'c> CompatSsoLoginRepository for PgCompatSsoLoginRepository<'c> {
.push_bind(Uuid::from(user.id)) .push_bind(Uuid::from(user.id))
.generate_pagination("cl.compat_sso_login_id", before, after, first, last)?; .generate_pagination("cl.compat_sso_login_id", before, after, first, last)?;
let page: Vec<CompatSsoLoginLookup> = query let edges: Vec<CompatSsoLoginLookup> = query
.build_query_as() .build_query_as()
.traced() .traced()
.fetch_all(&mut *self.conn) .fetch_all(&mut *self.conn)
.await?; .await?;
let (has_previous_page, has_next_page, edges) = process_page(page, first, last)?; let page = Page::process(edges, first, last)?.try_map(CompatSsoLogin::try_from)?;
Ok(page)
let edges: Result<Vec<_>, _> = edges.into_iter().map(TryInto::try_into).collect();
Ok(Page {
has_next_page,
has_previous_page,
edges: edges?,
})
} }
} }

View File

@ -21,7 +21,7 @@ use ulid::Ulid;
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
pagination::{process_page, Page, QueryBuilderExt}, pagination::{Page, QueryBuilderExt},
tracing::ExecuteExt, tracing::ExecuteExt,
Clock, DatabaseError, DatabaseInconsistencyError, LookupResultExt, Clock, DatabaseError, DatabaseInconsistencyError, LookupResultExt,
}; };
@ -271,15 +271,7 @@ impl<'c> OAuth2SessionRepository for PgOAuth2SessionRepository<'c> {
.fetch_all(&mut *self.conn) .fetch_all(&mut *self.conn)
.await?; .await?;
let (has_previous_page, has_next_page, edges) = process_page(edges, first, last)?; let page = Page::process(edges, first, last)?.try_map(Session::try_from)?;
Ok(page)
let edges: Result<Vec<_>, DatabaseInconsistencyError> =
edges.into_iter().map(Session::try_from).collect();
Ok(Page {
has_next_page,
has_previous_page,
edges: edges?,
})
} }
} }

View File

@ -82,39 +82,69 @@ where
Ok(()) Ok(())
} }
/// Process a page returned by a paginated query pub struct Page<T> {
pub fn process_page<T>( pub has_next_page: bool,
mut page: Vec<T>, pub has_previous_page: bool,
pub edges: Vec<T>,
}
impl<T> Page<T> {
/// Process a page returned by a paginated query
pub fn process(
mut edges: Vec<T>,
first: Option<usize>, first: Option<usize>,
last: Option<usize>, last: Option<usize>,
) -> Result<(bool, bool, Vec<T>), InvalidPagination> { ) -> Result<Self, InvalidPagination> {
let limit = match (first, last) { let limit = match (first, last) {
(Some(count), _) | (_, Some(count)) => count, (Some(count), _) | (_, Some(count)) => count,
_ => return Err(InvalidPagination), _ => return Err(InvalidPagination),
}; };
let is_full = page.len() == (limit + 1); let is_full = edges.len() == (limit + 1);
if is_full { if is_full {
page.pop(); edges.pop();
} }
let (has_previous_page, has_next_page) = if first.is_some() { let (has_previous_page, has_next_page) = if first.is_some() {
(false, is_full) (false, is_full)
} else if last.is_some() { } else if last.is_some() {
// 6. If the last argument is provided, I reverse the order of the results // 6. If the last argument is provided, I reverse the order of the results
page.reverse(); edges.reverse();
(is_full, false) (is_full, false)
} else { } else {
unreachable!() unreachable!()
}; };
Ok((has_previous_page, has_next_page, page)) Ok(Page {
} has_next_page,
has_previous_page,
edges,
})
}
pub struct Page<T> { pub fn map<F, T2>(self, f: F) -> Page<T2>
pub has_next_page: bool, where
pub has_previous_page: bool, F: FnMut(T) -> T2,
pub edges: Vec<T>, {
let edges = self.edges.into_iter().map(f).collect();
Page {
has_next_page: self.has_next_page,
has_previous_page: self.has_previous_page,
edges,
}
}
pub fn try_map<F, E, T2>(self, f: F) -> Result<Page<T2>, E>
where
F: FnMut(T) -> Result<T2, E>,
{
let edges: Result<Vec<T2>, E> = self.edges.into_iter().map(f).collect();
Ok(Page {
has_next_page: self.has_next_page,
has_previous_page: self.has_previous_page,
edges: edges?,
})
}
} }
impl<T> Page<T> {} impl<T> Page<T> {}

View File

@ -21,7 +21,7 @@ use ulid::Ulid;
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
pagination::{process_page, Page, QueryBuilderExt}, pagination::{Page, QueryBuilderExt},
tracing::ExecuteExt, tracing::ExecuteExt,
Clock, DatabaseError, LookupResultExt, Clock, DatabaseError, LookupResultExt,
}; };
@ -297,19 +297,13 @@ impl<'c> UpstreamOAuthLinkRepository for PgUpstreamOAuthLinkRepository<'c> {
.push_bind(Uuid::from(user.id)) .push_bind(Uuid::from(user.id))
.generate_pagination("upstream_oauth_link_id", before, after, first, last)?; .generate_pagination("upstream_oauth_link_id", before, after, first, last)?;
let page: Vec<LinkLookup> = query let edges: Vec<LinkLookup> = query
.build_query_as() .build_query_as()
.traced() .traced()
.fetch_all(&mut *self.conn) .fetch_all(&mut *self.conn)
.await?; .await?;
let (has_previous_page, has_next_page, edges) = process_page(page, first, last)?; let page = Page::process(edges, first, last)?.map(UpstreamOAuthLink::from);
Ok(page)
let edges: Vec<_> = edges.into_iter().map(Into::into).collect();
Ok(Page {
has_next_page,
has_previous_page,
edges,
})
} }
} }

View File

@ -23,7 +23,7 @@ use ulid::Ulid;
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
pagination::{process_page, Page, QueryBuilderExt}, pagination::{Page, QueryBuilderExt},
tracing::ExecuteExt, tracing::ExecuteExt,
Clock, DatabaseError, DatabaseInconsistencyError, LookupResultExt, Clock, DatabaseError, DatabaseInconsistencyError, LookupResultExt,
}; };
@ -266,20 +266,14 @@ impl<'c> UpstreamOAuthProviderRepository for PgUpstreamOAuthProviderRepository<'
query.generate_pagination("upstream_oauth_provider_id", before, after, first, last)?; query.generate_pagination("upstream_oauth_provider_id", before, after, first, last)?;
let page: Vec<ProviderLookup> = query let edges: Vec<ProviderLookup> = query
.build_query_as() .build_query_as()
.traced() .traced()
.fetch_all(&mut *self.conn) .fetch_all(&mut *self.conn)
.await?; .await?;
let (has_previous_page, has_next_page, edges) = process_page(page, first, last)?; let page = Page::process(edges, first, last)?.try_map(TryInto::try_into)?;
Ok(page)
let edges: Result<Vec<_>, _> = edges.into_iter().map(TryInto::try_into).collect();
Ok(Page {
has_next_page,
has_previous_page,
edges: edges?,
})
} }
#[tracing::instrument( #[tracing::instrument(

View File

@ -21,7 +21,7 @@ use ulid::Ulid;
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
pagination::{process_page, Page, QueryBuilderExt}, pagination::{Page, QueryBuilderExt},
tracing::ExecuteExt, tracing::ExecuteExt,
Clock, DatabaseError, DatabaseInconsistencyError, LookupResultExt, Clock, DatabaseError, DatabaseInconsistencyError, LookupResultExt,
}; };
@ -315,15 +315,8 @@ impl<'c> UserEmailRepository for PgUserEmailRepository<'c> {
.fetch_all(&mut *self.conn) .fetch_all(&mut *self.conn)
.await?; .await?;
let (has_previous_page, has_next_page, edges) = process_page(edges, first, last)?; let page = Page::process(edges, first, last)?.map(UserEmail::from);
Ok(page)
let edges = edges.into_iter().map(Into::into).collect();
Ok(Page {
has_next_page,
has_previous_page,
edges,
})
} }
#[tracing::instrument( #[tracing::instrument(

View File

@ -21,7 +21,7 @@ use ulid::Ulid;
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
pagination::{process_page, Page, QueryBuilderExt}, pagination::{Page, QueryBuilderExt},
tracing::ExecuteExt, tracing::ExecuteExt,
Clock, DatabaseError, DatabaseInconsistencyError, LookupResultExt, Clock, DatabaseError, DatabaseInconsistencyError, LookupResultExt,
}; };
@ -91,19 +91,19 @@ struct SessionLookup {
last_authd_at: Option<DateTime<Utc>>, last_authd_at: Option<DateTime<Utc>>,
} }
impl TryInto<BrowserSession> for SessionLookup { impl TryFrom<SessionLookup> for BrowserSession {
type Error = DatabaseInconsistencyError; type Error = DatabaseInconsistencyError;
fn try_into(self) -> Result<BrowserSession, Self::Error> { fn try_from(value: SessionLookup) -> Result<Self, Self::Error> {
let id = Ulid::from(self.user_id); let id = Ulid::from(value.user_id);
let user = User { let user = User {
id, id,
username: self.user_username, username: value.user_username,
sub: id.to_string(), sub: id.to_string(),
primary_user_email_id: self.user_primary_user_email_id.map(Into::into), primary_user_email_id: value.user_primary_user_email_id.map(Into::into),
}; };
let last_authentication = match (self.last_authentication_id, self.last_authd_at) { let last_authentication = match (value.last_authentication_id, value.last_authd_at) {
(Some(id), Some(created_at)) => Some(Authentication { (Some(id), Some(created_at)) => Some(Authentication {
id: id.into(), id: id.into(),
created_at, created_at,
@ -117,10 +117,10 @@ impl TryInto<BrowserSession> for SessionLookup {
}; };
Ok(BrowserSession { Ok(BrowserSession {
id: self.user_session_id.into(), id: value.user_session_id.into(),
user, user,
created_at: self.user_session_created_at, created_at: value.user_session_created_at,
finished_at: self.user_session_finished_at, finished_at: value.user_session_finished_at,
last_authentication, last_authentication,
}) })
} }
@ -292,20 +292,14 @@ impl<'c> BrowserSessionRepository for PgBrowserSessionRepository<'c> {
.push_bind(Uuid::from(user.id)) .push_bind(Uuid::from(user.id))
.generate_pagination("s.user_session_id", before, after, first, last)?; .generate_pagination("s.user_session_id", before, after, first, last)?;
let page: Vec<SessionLookup> = query let edges: Vec<SessionLookup> = query
.build_query_as() .build_query_as()
.traced() .traced()
.fetch_all(&mut *self.conn) .fetch_all(&mut *self.conn)
.await?; .await?;
let (has_previous_page, has_next_page, edges) = process_page(page, first, last)?; let page = Page::process(edges, first, last)?.try_map(BrowserSession::try_from)?;
Ok(page)
let edges: Result<Vec<_>, _> = edges.into_iter().map(TryInto::try_into).collect();
Ok(Page {
has_previous_page,
has_next_page,
edges: edges?,
})
} }
#[tracing::instrument( #[tracing::instrument(