diff --git a/constellation/src/server/mod.rs b/constellation/src/server/mod.rs index 9261fe8..09ca835 100644 --- a/constellation/src/server/mod.rs +++ b/constellation/src/server/mod.rs @@ -17,7 +17,7 @@ use tokio::net::{TcpListener, ToSocketAddrs}; use tokio::task::spawn_blocking; use tokio_util::sync::CancellationToken; -use crate::storage::{LinkReader, StorageStats}; +use crate::storage::{LinkReader, Order, StorageStats}; use crate::{CountsByCount, Did, RecordId}; mod acceptable; @@ -298,13 +298,19 @@ fn get_many_to_many_counts( let path_to_other = format!(".{}", query.path_to_other); + let order = if query.reverse { + Order::OldestToNewest + } else { + Order::NewestToOldest + }; + let paged = store .get_many_to_many_counts( &query.subject, collection, &path, &path_to_other, - query.reverse, + order, limit, cursor_key, &filter_dids, @@ -461,12 +467,18 @@ fn get_backlinks( }; let path = format!(".{path}"); + let order = if query.reverse { + Order::OldestToNewest + } else { + Order::NewestToOldest + }; + let paged = store .get_links( &query.subject, collection, &path, - query.reverse, + order, limit, until, &filter_dids, @@ -566,12 +578,18 @@ fn get_links( } } + let order = if query.reverse { + Order::OldestToNewest + } else { + Order::NewestToOldest + }; + let paged = store .get_links( &query.target, &query.collection, &query.path, - query.reverse, + order, limit, until, &filter_dids, diff --git a/constellation/src/storage/mem_store.rs b/constellation/src/storage/mem_store.rs index a5507fd..7915eeb 100644 --- a/constellation/src/storage/mem_store.rs +++ b/constellation/src/storage/mem_store.rs @@ -1,5 +1,6 @@ use super::{ - LinkReader, LinkStorage, PagedAppendingCollection, PagedOrderedCollection, StorageStats, + LinkReader, LinkStorage, Order, PagedAppendingCollection, PagedOrderedCollection, + StorageStats, }; use crate::{ActionableEvent, CountsByCount, Did, RecordId}; use anyhow::Result; @@ -140,7 +141,7 @@ impl LinkReader for MemStorage { collection: &str, path: &str, path_to_other: &str, - reverse: bool, + order: Order, limit: u64, after: Option, filter_dids: &HashSet, @@ -199,11 +200,10 @@ impl LinkReader for MemStorage { .iter() .map(|(k, (n, u, _))| (k.0.clone(), *n, u.len() as u64)) .collect(); - // sort in reverse order to show entries from oldest to newest - if reverse { - items.sort_by(|a, b| b.cmp(a)); - } else { - items.sort(); + // Sort based on order: OldestToNewest uses descending order, NewestToOldest uses ascending + match order { + Order::OldestToNewest => items.sort_by(|a, b| b.cmp(a)), + Order::NewestToOldest => items.sort(), } items = items .into_iter() @@ -250,7 +250,7 @@ impl LinkReader for MemStorage { target: &str, collection: &str, path: &str, - reverse: bool, + order: Order, limit: u64, until: Option, filter_dids: &HashSet, @@ -293,21 +293,26 @@ impl LinkReader for MemStorage { let end: usize; let next: Option; - if reverse { - begin = until.map(|u| (u) as usize).unwrap_or(0); - end = std::cmp::min(begin + limit as usize, total); - - next = if end < total { - Some(end as u64 + 1) - } else { - None - }; - } else { - end = until - .map(|u| std::cmp::min(u as usize, total)) - .unwrap_or(total); - begin = end.saturating_sub(limit as usize); - next = if begin == 0 { None } else { Some(begin as u64) }; + match order { + // OldestToNewest: start from the beginning, paginate forward + Order::OldestToNewest => { + begin = until.map(|u| (u) as usize).unwrap_or(0); + end = std::cmp::min(begin + limit as usize, total); + + next = if end < total { + Some(end as u64 + 1) + } else { + None + }; + } + // NewestToOldest: start from the end, paginate backward + Order::NewestToOldest => { + end = until + .map(|u| std::cmp::min(u as usize, total)) + .unwrap_or(total); + begin = end.saturating_sub(limit as usize); + next = if begin == 0 { None } else { Some(begin as u64) }; + } } let alive = did_rkeys.iter().flatten().count(); @@ -325,7 +330,8 @@ impl LinkReader for MemStorage { }) .collect(); - if reverse { + // For OldestToNewest, reverse the items to maintain forward chronological order + if order == Order::OldestToNewest { items.reverse(); } diff --git a/constellation/src/storage/mod.rs b/constellation/src/storage/mod.rs index d090ad4..77be8b9 100644 --- a/constellation/src/storage/mod.rs +++ b/constellation/src/storage/mod.rs @@ -11,6 +11,15 @@ pub mod rocks_store; #[cfg(feature = "rocks")] pub use rocks_store::RocksStorage; +/// Ordering for paginated link queries +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Order { + /// Newest links first (default) + NewestToOldest, + /// Oldest links first + OldestToNewest, +} + #[derive(Debug, PartialEq)] pub struct PagedAppendingCollection { pub version: (u64, u64), // (collection length, deleted item count) // TODO: change to (total, active)? since dedups isn't "deleted" @@ -72,7 +81,7 @@ pub trait LinkReader: Clone + Send + Sync + 'static { collection: &str, path: &str, path_to_other: &str, - reverse: bool, + order: Order, limit: u64, after: Option, filter_dids: &HashSet, @@ -88,7 +97,7 @@ pub trait LinkReader: Clone + Send + Sync + 'static { target: &str, collection: &str, path: &str, - reverse: bool, + order: Order, limit: u64, until: Option, filter_dids: &HashSet, @@ -182,7 +191,7 @@ mod tests { "a.com", "app.t.c", ".abc.uri", - false, + Order::NewestToOldest, 100, None, &HashSet::default() @@ -686,7 +695,7 @@ mod tests { "a.com", "app.t.c", ".abc.uri", - false, + Order::NewestToOldest, 100, None, &HashSet::default() @@ -735,7 +744,7 @@ mod tests { "a.com", "app.t.c", ".abc.uri", - false, + Order::NewestToOldest, 2, None, &HashSet::default(), @@ -774,7 +783,7 @@ mod tests { "a.com", "app.t.c", ".abc.uri", - false, + Order::NewestToOldest, 2, links.next, &HashSet::default(), @@ -813,7 +822,7 @@ mod tests { "a.com", "app.t.c", ".abc.uri", - false, + Order::NewestToOldest, 2, links.next, &HashSet::default(), @@ -862,12 +871,12 @@ mod tests { )?; } - // Test reverse: true (oldest first) + // Test OldestToNewest order (oldest first) let links = storage.get_links( "a.com", "app.t.c", ".abc.uri", - true, + Order::OldestToNewest, 2, None, &HashSet::default(), @@ -892,12 +901,12 @@ mod tests { total: 5, } ); - // Test reverse: false (newest first) + // Test NewestToOldest order (newest first) let links = storage.get_links( "a.com", "app.t.c", ".abc.uri", - false, + Order::NewestToOldest, 2, None, &HashSet::default(), @@ -930,7 +939,7 @@ mod tests { "a.com", "app.t.c", ".abc.uri", - false, + Order::NewestToOldest, 2, None, &HashSet::from([Did("did:plc:linker".to_string())]), @@ -964,7 +973,7 @@ mod tests { "a.com", "app.t.c", ".abc.uri", - false, + Order::NewestToOldest, 2, None, &HashSet::from([Did("did:plc:linker".to_string())]), @@ -987,7 +996,7 @@ mod tests { "a.com", "app.t.c", ".abc.uri", - false, + Order::NewestToOldest, 2, None, &HashSet::from([Did("did:plc:someone-else".to_string())]), @@ -1035,7 +1044,7 @@ mod tests { "a.com", "app.t.c", ".abc.uri", - false, + Order::NewestToOldest, 2, None, &HashSet::from([Did("did:plc:linker".to_string())]), @@ -1065,7 +1074,7 @@ mod tests { "a.com", "app.t.c", ".abc.uri", - false, + Order::NewestToOldest, 2, None, &HashSet::from([ @@ -1098,7 +1107,7 @@ mod tests { "a.com", "app.t.c", ".abc.uri", - false, + Order::NewestToOldest, 2, None, &HashSet::from([Did("did:plc:someone-unknown".to_string())]), @@ -1135,7 +1144,7 @@ mod tests { "a.com", "app.t.c", ".abc.uri", - false, + Order::NewestToOldest, 2, None, &HashSet::default(), @@ -1164,7 +1173,7 @@ mod tests { "a.com", "app.t.c", ".abc.uri", - false, + Order::NewestToOldest, 2, links.next, &HashSet::default(), @@ -1213,7 +1222,7 @@ mod tests { "a.com", "app.t.c", ".abc.uri", - false, + Order::NewestToOldest, 2, None, &HashSet::default(), @@ -1256,7 +1265,7 @@ mod tests { "a.com", "app.t.c", ".abc.uri", - false, + Order::NewestToOldest, 2, links.next, &HashSet::default(), @@ -1305,7 +1314,7 @@ mod tests { "a.com", "app.t.c", ".abc.uri", - false, + Order::NewestToOldest, 2, None, &HashSet::default(), @@ -1342,7 +1351,7 @@ mod tests { "a.com", "app.t.c", ".abc.uri", - false, + Order::NewestToOldest, 2, links.next, &HashSet::default(), @@ -1384,7 +1393,7 @@ mod tests { "a.com", "app.t.c", ".abc.uri", - false, + Order::NewestToOldest, 2, None, &HashSet::default(), @@ -1417,7 +1426,7 @@ mod tests { "a.com", "app.t.c", ".abc.uri", - false, + Order::NewestToOldest, 2, links.next, &HashSet::default(), @@ -1499,7 +1508,7 @@ mod tests { "a.b.c", ".d.e", ".f.g", - false, + Order::NewestToOldest, 10, None, &HashSet::new(), @@ -1543,7 +1552,7 @@ mod tests { "app.t.c", ".abc.uri", ".def.uri", - false, + Order::NewestToOldest, 10, None, &HashSet::new(), @@ -1643,7 +1652,7 @@ mod tests { "app.t.c", ".abc.uri", ".def.uri", - false, + Order::NewestToOldest, 10, None, &HashSet::new(), @@ -1660,7 +1669,7 @@ mod tests { "app.t.c", ".abc.uri", ".def.uri", - false, + Order::NewestToOldest, 10, None, &HashSet::from_iter([Did("did:plc:fdsa".to_string())]), @@ -1677,7 +1686,7 @@ mod tests { "app.t.c", ".abc.uri", ".def.uri", - false, + Order::NewestToOldest, 10, None, &HashSet::new(), @@ -1753,13 +1762,13 @@ mod tests { 2, )?; - // Test reverse: false (default order - by target ascending) + // Test NewestToOldest order (default order - by target ascending) let counts = storage.get_many_to_many_counts( "a.com", "app.t.c", ".abc.uri", ".def.uri", - false, + Order::NewestToOldest, 10, None, &HashSet::new(), @@ -1771,13 +1780,13 @@ mod tests { assert_eq!(counts.items[1].0, "c.com"); assert_eq!(counts.items[2].0, "d.com"); - // Test reverse: true (descending order - by target descending) + // Test OldestToNewest order (descending order - by target descending) let counts = storage.get_many_to_many_counts( "a.com", "app.t.c", ".abc.uri", ".def.uri", - true, + Order::OldestToNewest, 10, None, &HashSet::new(), diff --git a/constellation/src/storage/rocks_store.rs b/constellation/src/storage/rocks_store.rs index 2838fbb..464f93b 100644 --- a/constellation/src/storage/rocks_store.rs +++ b/constellation/src/storage/rocks_store.rs @@ -1,6 +1,6 @@ use super::{ - ActionableEvent, LinkReader, LinkStorage, PagedAppendingCollection, PagedOrderedCollection, - StorageStats, + ActionableEvent, LinkReader, LinkStorage, Order, PagedAppendingCollection, + PagedOrderedCollection, StorageStats, }; use crate::{CountsByCount, Did, RecordId}; use anyhow::{bail, Result}; @@ -941,7 +941,7 @@ impl LinkReader for RocksStorage { collection: &str, path: &str, path_to_other: &str, - reverse: bool, + order: Order, limit: u64, after: Option, filter_dids: &HashSet, @@ -1084,11 +1084,10 @@ impl LinkReader for RocksStorage { items.push((target.0 .0, *n, dids.len() as u64)); } - // Sort in desired direction - if reverse { - items.sort_by(|a, b| b.cmp(a)); // descending - } else { - items.sort(); // ascending + // Sort based on order: OldestToNewest uses descending order, NewestToOldest uses ascending + match order { + Order::OldestToNewest => items.sort_by(|a, b| b.cmp(a)), // descending + Order::NewestToOldest => items.sort(), // ascending } let next = if grouped_counts.len() as u64 >= limit { @@ -1136,7 +1135,7 @@ impl LinkReader for RocksStorage { target: &str, collection: &str, path: &str, - reverse: bool, + order: Order, limit: u64, until: Option, filter_dids: &HashSet, @@ -1182,24 +1181,30 @@ impl LinkReader for RocksStorage { let begin: usize; let next: Option; - if reverse { - begin = until.map(|u| (u - 1) as usize).unwrap_or(0); - end = std::cmp::min(begin + limit as usize, total as usize); + match order { + // OldestToNewest: start from the beginning, paginate forward + Order::OldestToNewest => { + begin = until.map(|u| (u - 1) as usize).unwrap_or(0); + end = std::cmp::min(begin + limit as usize, total as usize); - next = if end < total as usize { - Some(end as u64 + 1) - } else { - None + next = if end < total as usize { + Some(end as u64 + 1) + } else { + None + } + } + // NewestToOldest: start from the end, paginate backward + Order::NewestToOldest => { + end = until.map(|u| std::cmp::min(u, total)).unwrap_or(total) as usize; + begin = end.saturating_sub(limit as usize); + next = if begin == 0 { None } else { Some(begin as u64) }; } - } else { - end = until.map(|u| std::cmp::min(u, total)).unwrap_or(total) as usize; - begin = end.saturating_sub(limit as usize); - next = if begin == 0 { None } else { Some(begin as u64) }; } let mut did_id_rkeys = linkers.0[begin..end].iter().rev().collect::>(); - if reverse { + // For OldestToNewest, reverse the items to maintain forward chronological order + if order == Order::OldestToNewest { did_id_rkeys.reverse(); }