diff --git a/mm2src/mm2_db/src/indexed_db/drivers/cursor/cursor.rs b/mm2src/mm2_db/src/indexed_db/drivers/cursor/cursor.rs index 6a534b1cd9..0cba8c8402 100644 --- a/mm2src/mm2_db/src/indexed_db/drivers/cursor/cursor.rs +++ b/mm2src/mm2_db/src/indexed_db/drivers/cursor/cursor.rs @@ -20,7 +20,6 @@ mod multi_key_cursor; mod single_key_bound_cursor; mod single_key_cursor; -use crate::indexed_db::indexed_cursor::CursorCondition; use empty_cursor::IdbEmptyCursor; use multi_key_bound_cursor::IdbMultiKeyBoundCursor; use multi_key_cursor::IdbMultiKeyCursor; @@ -28,6 +27,7 @@ use single_key_bound_cursor::IdbSingleKeyBoundCursor; use single_key_cursor::IdbSingleKeyCursor; pub type CursorResult = Result>; +type CursorCondition = Box CursorResult + Send + 'static>; #[derive(Debug, Display, EnumFromTrait, PartialEq)] pub enum CursorError { @@ -84,13 +84,28 @@ pub enum CursorBoundValue { BigUint(BeBigUint), } +/// Represents criteria for filtering and refining results when using database cursors. #[derive(Default)] pub struct CursorFilters { + /// Specifies key-value pairs to filter results by. pub(crate) only_keys: Vec<(String, Json)>, + /// Specifies range filters for keys.. pub(crate) bound_keys: Vec<(String, CursorBoundValue, CursorBoundValue)>, + /// Indicates whether to sort results in reverse order. pub(crate) reverse: bool, } +/// Provides extended filtering options for cursor-based queries. +#[derive(Default)] +pub struct CursorFiltersExt { + /// An optional filter expression. + pub(crate) where_: Option, + /// The maximum number of results to return. + pub(crate) limit: Option, + /// The number of results to skip before returning. + pub(crate) offset: Option, +} + impl From for CursorBoundValue { fn from(uint: u32) -> Self { CursorBoundValue::Uint(uint) } } @@ -192,6 +207,7 @@ pub trait CursorDriverImpl: Sized { pub(crate) struct CursorDriver { /// An actual cursor implementation. inner: IdbCursorEnum, + filters_ext: CursorFiltersExt, cursor_request: IdbRequest, cursor_item_rx: mpsc::Receiver>, /// Whether we got `CursorAction::Stop` at the last iteration or not. @@ -202,7 +218,11 @@ pub(crate) struct CursorDriver { } impl CursorDriver { - pub(crate) fn init_cursor(db_index: IdbIndex, filters: CursorFilters) -> CursorResult { + pub(crate) fn init_cursor( + db_index: IdbIndex, + filters: CursorFilters, + filters_ext: CursorFiltersExt, + ) -> CursorResult { let reverse = filters.reverse; let inner = IdbCursorEnum::new(filters)?; @@ -234,6 +254,7 @@ impl CursorDriver { Ok(CursorDriver { inner, + filters_ext, cursor_request, cursor_item_rx, stopped: false, @@ -242,19 +263,11 @@ impl CursorDriver { }) } - pub(crate) async fn next(&mut self, where_: Option) -> CursorResult> { - loop { - if self.stopped { - return Ok(None); - } - - match self.process_cursor_item(where_.as_ref()).await? { - Some(result) => return Ok(Some(result)), - None => continue, - } - } - } - + /// Continues the cursor according to the provided `CursorAction`. + /// If the action is `CursorAction::Continue`, the cursor advances to the next item. + /// If the action is `CursorAction::ContinueWithValue`, the cursor advances to the specified value. + /// If the action is `CursorAction::Stop`, the cursor is stopped, and subsequent calls to `next` + /// will return `None`. async fn continue_(&mut self, cursor: &IdbCursorWithValue, cursor_action: &CursorAction) -> CursorResult<()> { match cursor_action { CursorAction::Continue => cursor.continue_().map_to_mm(|e| CursorError::AdvanceError { @@ -277,7 +290,28 @@ impl CursorDriver { Ok(()) } - async fn process_cursor_item(&mut self, where_: Option<&CursorCondition>) -> CursorResult> { + /// Advances the cursor by the offset specified in the `filters_ext.offset` field. + /// This operation is typically performed once at the beginning of cursor-based iteration. + /// After the offset is applied, the value in `filters_ext.offset` is cleared. + /// An error will be thrown if the cursor is currently being iterated or has iterated past its end. + // https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/advance + async fn advance_by_offset(&mut self) -> CursorResult<()> { + if let Some(offset) = self.filters_ext.offset.take() { + if let Some(cursor) = self.get_cursor_or_stop().await? { + cursor.advance(offset).map_to_mm(|e| CursorError::AdvanceError { + description: stringify_js_error(&e), + })?; + } else { + self.stopped = true; + } + } + + Ok(()) + } + + /// Helper function to retrieve a cursor or indicate the processing should stop. + /// Handles potential errors related to opening the cursor and receiving events. + async fn get_cursor_or_stop(&mut self) -> CursorResult> { let event = match self.cursor_item_rx.next().await { Some(event) => event, None => { @@ -290,14 +324,39 @@ impl CursorDriver { description: stringify_js_error(&e), })?; - let cursor = match cursor_from_request(&self.cursor_request)? { + cursor_from_request(&self.cursor_request) + } + + /// Continuously processes cursor items until it retrieves a valid result or + /// the cursor is stopped. It returns a `CursorResult` containing either the next item + /// wrapped in `Some`, or `None` if the cursor is stopped. + pub(crate) async fn next(&mut self) -> CursorResult> { + // Handle offset on first iteration if there's any. + self.advance_by_offset().await?; + + loop { + if self.stopped { + return Ok(None); + } + + match self.process_cursor_item().await? { + Some(result) => return Ok(Some(result)), + None => continue, + } + } + } + + /// Processes the next item from the cursor, which includes fetching the cursor event, + /// opening the cursor, deserializing the item, and performing actions based on the item and cursor conditions. + /// It returns an `Option` containing the item ID and value if an item is processed successfully, otherwise `None`. + async fn process_cursor_item(&mut self) -> CursorResult> { + let cursor = match self.get_cursor_or_stop().await? { Some(cursor) => cursor, None => { self.stopped = true; return Ok(None); }, }; - let (key, js_value) = match (cursor.key(), cursor.value()) { (Ok(key), Ok(js_value)) => (key, js_value), _ => { @@ -311,18 +370,22 @@ impl CursorDriver { let (item_action, cursor_action) = self.inner.on_iteration(key)?; let (id, val) = item.into_pair(); + // Checks if the given `where_` condition, represented by an optional closure (`cursor_condition`), // is satisfied for the provided `item`. If the condition is met, return the corresponding `(id, val)` or skip to the next item. - if matches!(item_action, CursorItemAction::Include) { - if let Some(cursor_condition) = where_ { + if let Some(cursor_condition) = &self.filters_ext.where_ { if cursor_condition(val.clone())? { - // stop iteration and return value. - self.stopped = true; + // Update limit (if applicable) and return + if self.filters_ext.limit.is_some() { + self.update_limit_and_continue(&cursor, &cursor_action).await?; + } else { + self.stopped = true; + }; return Ok(Some((id, val))); } } else { - self.continue_(&cursor, &cursor_action).await?; + self.update_limit_and_continue(&cursor, &cursor_action).await?; return Ok(Some((id, val))); }; } @@ -330,6 +393,29 @@ impl CursorDriver { self.continue_(&cursor, &cursor_action).await?; Ok(None) } + + /// Checks the current limit set for the cursor. If the limit is greater than 1, + /// it decrements the limit by 1. If the limit becomes 1 or less, it sets the `stopped` flag + /// to true, indicating that the cursor should stop. + async fn update_limit_and_continue( + &mut self, + cursor: &IdbCursorWithValue, + cursor_action: &CursorAction, + ) -> CursorResult<()> { + if let Some(limit) = self.filters_ext.limit { + // Early return if limit is reached + if limit <= 1 { + self.stopped = true; + return Ok(()); + } + + // Decrement limit and continue + self.filters_ext.limit = Some(limit - 1); + return self.continue_(cursor, cursor_action).await; + }; + + self.continue_(cursor, cursor_action).await + } } pub(crate) enum IdbCursorEnum { diff --git a/mm2src/mm2_db/src/indexed_db/indexed_cursor.rs b/mm2src/mm2_db/src/indexed_db/indexed_cursor.rs index 096733e68a..929057e074 100644 --- a/mm2src/mm2_db/src/indexed_db/indexed_cursor.rs +++ b/mm2src/mm2_db/src/indexed_db/indexed_cursor.rs @@ -54,7 +54,7 @@ use crate::indexed_db::db_driver::cursor::CursorBoundValue; pub(crate) use crate::indexed_db::db_driver::cursor::{CursorDriver, CursorFilters}; -pub use crate::indexed_db::db_driver::cursor::{CursorError, CursorResult}; +pub use crate::indexed_db::db_driver::cursor::{CursorError, CursorFiltersExt, CursorResult}; use crate::indexed_db::{DbTable, ItemId, TableSignature}; use futures::channel::{mpsc, oneshot}; use futures::{SinkExt, StreamExt}; @@ -66,20 +66,19 @@ use std::marker::PhantomData; pub(super) type DbCursorEventTx = mpsc::UnboundedSender; pub(super) type DbCursorEventRx = mpsc::UnboundedReceiver; -pub(super) type CursorCondition = Box CursorResult + Send + 'static>; pub struct CursorBuilder<'transaction, 'reference, Table: TableSignature> { db_table: &'reference DbTable<'transaction, Table>, filters: CursorFilters, - where_: Option, + filters_ext: CursorFiltersExt, } impl<'transaction, 'reference, Table: TableSignature> CursorBuilder<'transaction, 'reference, Table> { pub(crate) fn new(db_table: &'reference DbTable<'transaction, Table>) -> Self { CursorBuilder { db_table, - where_: None, filters: CursorFilters::default(), + filters_ext: CursorFiltersExt::default(), } } @@ -134,7 +133,7 @@ impl<'transaction, 'reference, Table: TableSignature> CursorBuilder<'transaction where F: Fn(Json) -> CursorResult + Send + 'static, { - self.where_ = Some(Box::new(f)); + self.filters_ext.where_ = Some(Box::new(f)); self } @@ -145,19 +144,28 @@ impl<'transaction, 'reference, Table: TableSignature> CursorBuilder<'transaction /// ``` pub fn where_first(self) -> CursorBuilder<'transaction, 'reference, Table> { self.where_(|_| Ok(true)) } + pub fn limit(mut self, limit: usize) -> CursorBuilder<'transaction, 'reference, Table> { + self.filters_ext.limit = Some(limit); + self + } + + pub fn offset(mut self, offset: u32) -> CursorBuilder<'transaction, 'reference, Table> { + self.filters_ext.offset = Some(offset); + self + } + /// Opens a cursor by the specified `index`. /// https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/openCursor pub async fn open_cursor(self, index: &str) -> CursorResult> { - let event_tx = - self.db_table - .open_cursor(index, self.filters) - .await - .mm_err(|e| CursorError::ErrorOpeningCursor { - description: e.to_string(), - })?; + let event_tx = self + .db_table + .open_cursor(index, self.filters, self.filters_ext) + .await + .mm_err(|e| CursorError::ErrorOpeningCursor { + description: e.to_string(), + })?; Ok(CursorIter { event_tx, - where_: self.where_, phantom: PhantomData::default(), }) } @@ -165,7 +173,6 @@ impl<'transaction, 'reference, Table: TableSignature> CursorBuilder<'transaction pub struct CursorIter<'transaction, Table> { event_tx: DbCursorEventTx, - where_: Option, phantom: PhantomData<&'transaction Table>, } @@ -175,10 +182,7 @@ impl<'transaction, Table: TableSignature> CursorIter<'transaction, Table> { pub async fn next(&mut self) -> CursorResult> { let (result_tx, result_rx) = oneshot::channel(); self.event_tx - .send(DbCursorEvent::NextItem { - result_tx, - where_: self.where_.take(), - }) + .send(DbCursorEvent::NextItem { result_tx }) .await .map_to_mm(|e| CursorError::UnexpectedState(format!("Error sending cursor event: {e}")))?; let maybe_item = result_rx @@ -204,15 +208,14 @@ impl<'transaction, Table: TableSignature> CursorIter<'transaction, Table> { pub enum DbCursorEvent { NextItem { result_tx: oneshot::Sender>>, - where_: Option, }, } pub(crate) async fn cursor_event_loop(mut rx: DbCursorEventRx, mut cursor: CursorDriver) { while let Some(event) = rx.next().await { match event { - DbCursorEvent::NextItem { result_tx, where_ } => { - result_tx.send(cursor.next(where_).await).ok(); + DbCursorEvent::NextItem { result_tx } => { + result_tx.send(cursor.next().await).ok(); }, } } @@ -282,13 +285,13 @@ mod tests { } } - async fn fill_table(table: &DbTable<'_, Table>, items: Vec
) + async fn fill_table
(table: &DbTable<'_, Table>, items: &Vec
) where Table: TableSignature + std::fmt::Debug, { for item in items { table - .add_item(&item) + .add_item(item) .await .unwrap_or_else(|_| panic!("Error adding {:?} item", item)); } @@ -388,7 +391,7 @@ mod tests { .table::() .await .expect("!DbTransaction::open_table"); - fill_table(&table, items).await; + fill_table(&table, &items).await; // Test the cursor index for each combination of numbers (lower, upper). for num_x in numbers.iter() { @@ -429,8 +432,6 @@ mod tests { const DB_NAME: &str = "TEST_COLLECT_SINGLE_KEY_CURSOR"; const DB_VERSION: u32 = 1; - register_wasm_log(); - let items = vec![ swap_item!("uuid1", "RICK", "MORTY", 10, 1, 700), // + swap_item!("uuid2", "MORTY", "KMD", 95000, 1, 721), @@ -451,7 +452,7 @@ mod tests { .table::() .await .expect("!DbTransaction::open_table"); - fill_table(&table, items).await; + fill_table(&table, &items).await; let mut actual_items = table .cursor_builder() @@ -483,8 +484,6 @@ mod tests { const DB_NAME: &str = "TEST_COLLECT_SINGLE_KEY_BOUND_CURSOR"; const DB_VERSION: u32 = 1; - register_wasm_log(); - let items = vec![ swap_item!("uuid1", "RICK", "MORTY", 10, 3, 700), swap_item!("uuid2", "MORTY", "KMD", 95000, 1, 721), @@ -505,7 +504,7 @@ mod tests { .table::() .await .expect("!DbTransaction::open_table"); - fill_table(&table, items).await; + fill_table(&table, &items).await; let mut actual_items = table .cursor_builder() @@ -536,8 +535,6 @@ mod tests { const DB_NAME: &str = "TEST_COLLECT_MULTI_KEY_CURSOR"; const DB_VERSION: u32 = 1; - register_wasm_log(); - let items = vec![ swap_item!("uuid1", "RICK", "MORTY", 12, 1, 700), swap_item!("uuid2", "RICK", "KMD", 95000, 6, 721), @@ -564,7 +561,7 @@ mod tests { .table::() .await .expect("!DbTransaction::open_table"); - fill_table(&table, items).await; + fill_table(&table, &items).await; let mut actual_items = table .cursor_builder() @@ -600,8 +597,6 @@ mod tests { const DB_NAME: &str = "TEST_COLLECT_MULTI_KEY_BOUND_CURSOR"; const DB_VERSION: u32 = 1; - register_wasm_log(); - let items = vec![ swap_item!("uuid1", "MORTY", "RICK", 12, 10, 999), swap_item!("uuid2", "RICK", "QRC20", 4, 12, 557), @@ -641,7 +636,7 @@ mod tests { .table::() .await .expect("!DbTransaction::open_table"); - fill_table(&table, items).await; + fill_table(&table, &items).await; let actual_items = table .cursor_builder() @@ -678,8 +673,6 @@ mod tests { const DB_NAME: &str = "TEST_COLLECT_MULTI_KEY_BOUND_CURSOR_BIG_INT"; const DB_VERSION: u32 = 1; - register_wasm_log(); - let items = vec![ TimestampTable::new(u64::MAX, 6, u128::MAX - 3), TimestampTable::new(u64::MAX - 1, 0, u128::MAX - 2), // + @@ -702,7 +695,7 @@ mod tests { .table::() .await .expect("!DbTransaction::open_table"); - fill_table(&table, items).await; + fill_table(&table, &items).await; let actual_items = table .cursor_builder() @@ -735,8 +728,6 @@ mod tests { const DB_NAME: &str = "TEST_ITER_WITHOUT_CONSTRAINTS"; const DB_VERSION: u32 = 1; - register_wasm_log(); - let items = vec![ swap_item!("uuid1", "RICK", "MORTY", 10, 3, 700), swap_item!("uuid2", "MORTY", "KMD", 95000, 1, 721), @@ -755,7 +746,7 @@ mod tests { .table::() .await .expect("!DbTransaction::open_table"); - fill_table(&table, items).await; + fill_table(&table, &items).await; let mut cursor_iter = table .cursor_builder() @@ -790,8 +781,6 @@ mod tests { const DB_NAME: &str = "TEST_REV_ITER_WITHOUT_CONSTRAINTS"; const DB_VERSION: u32 = 1; - register_wasm_log(); - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) .with_version(DB_VERSION) .with_table::() @@ -820,8 +809,6 @@ mod tests { const DB_NAME: &str = "TEST_ITER_SINGLE_KEY_BOUND_CURSOR"; const DB_VERSION: u32 = 1; - register_wasm_log(); - let items = vec![ swap_item!("uuid1", "RICK", "MORTY", 10, 3, 700), swap_item!("uuid2", "MORTY", "KMD", 95000, 1, 721), @@ -842,7 +829,7 @@ mod tests { .table::() .await .expect("!DbTransaction::open_table"); - fill_table(&table, items).await; + fill_table(&table, &items).await; let mut cursor_iter = table .cursor_builder() @@ -873,9 +860,6 @@ mod tests { async fn test_rev_iter_single_key_bound_cursor() { const DB_NAME: &str = "TEST_REV_ITER_SINGLE_KEY_BOUND_CURSOR"; const DB_VERSION: u32 = 1; - - register_wasm_log(); - let items = vec![ swap_item!("uuid1", "RICK", "MORTY", 10, 3, 700), swap_item!("uuid2", "MORTY", "KMD", 95000, 1, 721), @@ -896,7 +880,7 @@ mod tests { .table::() .await .expect("!DbTransaction::open_table"); - fill_table(&table, items).await; + fill_table(&table, &items).await; let mut cursor_iter = table .cursor_builder() @@ -929,8 +913,6 @@ mod tests { const DB_NAME: &str = "TEST_REV_ITER_SINGLE_KEY_BOUND_CURSOR"; const DB_VERSION: u32 = 1; - register_wasm_log(); - let items = vec![ swap_item!("uuid1", "RICK", "MORTY", 10, 3, 700), swap_item!("uuid2", "MORTY", "KMD", 95000, 1, 721), @@ -951,7 +933,7 @@ mod tests { .table::() .await .expect("!DbTransaction::open_table"); - fill_table(&table, items).await; + fill_table(&table, &items).await; // check for first swap where started_at is 1281. let condition = move |swap| { @@ -979,8 +961,6 @@ mod tests { const DB_NAME: &str = "TEST_REV_ITER_SINGLE_KEY_BOUND_CURSOR"; const DB_VERSION: u32 = 1; - register_wasm_log(); - let items = vec![ swap_item!("uuid1", "RICK", "MORTY", 10, 3, 700), swap_item!("uuid2", "MORTY", "KMD", 95000, 1, 721), @@ -1001,7 +981,7 @@ mod tests { .table::() .await .expect("!DbTransaction::open_table"); - fill_table(&table, items).await; + fill_table(&table, &items).await; let maybe_swap = table .cursor_builder() @@ -1024,8 +1004,6 @@ mod tests { const DB_NAME: &str = "TEST_REV_ITER_SINGLE_KEY_BOUND_CURSOR"; const DB_VERSION: u32 = 1; - register_wasm_log(); - let items = vec![ swap_item!("uuid1", "RICK", "MORTY", 10, 3, 700), swap_item!("uuid2", "MORTY", "KMD", 95000, 1, 721), @@ -1046,7 +1024,7 @@ mod tests { .table::() .await .expect("!DbTransaction::open_table"); - fill_table(&table, items).await; + fill_table(&table, &items).await; let maybe_swap = table .cursor_builder() @@ -1066,11 +1044,55 @@ mod tests { } #[wasm_bindgen_test] - async fn test_cursor_where_first_condition_with_limit() { + async fn test_cursor_where_condition_with_limit() { const DB_NAME: &str = "TEST_REV_ITER_SINGLE_KEY_BOUND_CURSOR"; const DB_VERSION: u32 = 1; - register_wasm_log(); + let items = vec![ + swap_item!("uuid1", "RICK", "MORTY", 10, 3, 700), + swap_item!("uuid2", "MORTY", "KMD", 95000, 1, 721), + swap_item!("uuid3", "RICK", "XYZ", 7, u32::MAX, 1281), // + + swap_item!("uuid4", "RICK", "MORTY", 8, 6, 92), // + + swap_item!("uuid5", "QRC20", "RICK", 2, 4, 721), + swap_item!("uuid6", "KMD", "MORTY", 12, 3124, 214), // + + ]; + + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + .with_version(DB_VERSION) + .with_table::() + .build() + .await + .expect("!IndexedDb::init"); + let transaction = db.transaction().await.expect("!IndexedDb::transaction"); + let table = transaction + .table::() + .await + .expect("!DbTransaction::open_table"); + fill_table(&table, &items).await; + + let maybe_swaps = table + .cursor_builder() + .bound("rel_coin_value", 5u32, u32::MAX) + .where_(|_| Ok(true)) + .limit(1) + .open_cursor("rel_coin_value") + .await + .expect("!CursorBuilder::open_cursor") + .collect() + .await + .expect("!CursorBuilder::open_cursor") + .into_iter() + .map(|(_, swap)| swap) + .collect::>(); + + let expected_swaps = vec![swap_item!("uuid4", "RICK", "MORTY", 8, 6, 92)]; + assert_eq!(expected_swaps, maybe_swaps) + } + + #[wasm_bindgen_test] + async fn test_cursor_with_limit() { + const DB_NAME: &str = "TEST_REV_ITER_SINGLE_KEY_BOUND_CURSOR"; + const DB_VERSION: u32 = 1; let items = vec![ swap_item!("uuid1", "RICK", "MORTY", 10, 3, 700), @@ -1092,21 +1114,72 @@ mod tests { .table::() .await .expect("!DbTransaction::open_table"); - fill_table(&table, items).await; + fill_table(&table, &items).await; - let maybe_swap = table + let maybe_swaps = table .cursor_builder() .bound("rel_coin_value", 5u32, u32::MAX) - .where_first() + .limit(2) .open_cursor("rel_coin_value") .await .expect("!CursorBuilder::open_cursor") - .next() + .collect() .await - .expect("!Cursor next result") - .map(|(_, swap)| swap); + .expect("!CursorBuilder::collect") + .into_iter() + .map(|(_, swap)| swap) + .collect::>(); - // maybe_swap should return swap with uuid4 since it's the item with the lowest rel_coin_value in the store. - assert_eq!(maybe_swap, Some(swap_item!("uuid4", "RICK", "MORTY", 8, 6, 92))); + let expected_swaps = vec![ + swap_item!("uuid4", "RICK", "MORTY", 8, 6, 92), + swap_item!("uuid6", "KMD", "MORTY", 12, 3124, 214), + ]; + assert_eq!(expected_swaps, maybe_swaps) + } + + #[wasm_bindgen_test] + async fn test_cursor_with_offset_and_limit() { + const DB_NAME: &str = "TEST_REV_ITER_SINGLE_KEY_BOUND_CURSOR"; + const DB_VERSION: u32 = 1; + + register_wasm_log(); + + let items = vec![ + swap_item!("uuid1", "RICK", "XYZ", 7, u32::MAX, 1281), + swap_item!("uuid2", "RICK", "MORTY", 8, 6, 92), + swap_item!("uuid3", "RICK", "FTM", 12, 3124, 214), + ]; + + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + .with_version(DB_VERSION) + .with_table::() + .build() + .await + .expect("!IndexedDb::init"); + let transaction = db.transaction().await.expect("!IndexedDb::transaction"); + let table = transaction + .table::() + .await + .expect("!DbTransaction::open_table"); + fill_table(&table, &items).await; + + let maybe_swaps = table + .cursor_builder() + .only("base_coin", "RICK") + .expect("!CursorBuilder::only") + .offset(1) + .limit(1) + .open_cursor("base_coin") + .await + .expect("!CursorBuilder::open_cursor") + .collect() + .await + .expect("!CursorBuilder::open_cursor") + .into_iter() + .map(|(_, swap)| swap) + .collect::>(); + + let expected_swaps = vec![swap_item!("uuid2", "RICK", "MORTY", 8, 6, 92)]; + assert_eq!(expected_swaps, maybe_swaps) } } diff --git a/mm2src/mm2_db/src/indexed_db/indexed_db.rs b/mm2src/mm2_db/src/indexed_db/indexed_db.rs index 9521938ef6..39c81ec620 100644 --- a/mm2src/mm2_db/src/indexed_db/indexed_db.rs +++ b/mm2src/mm2_db/src/indexed_db/indexed_db.rs @@ -51,8 +51,8 @@ pub use db_driver::{DbTransactionError, DbTransactionResult, DbUpgrader, InitDbE pub use db_lock::{ConstructibleDb, DbLocked, SharedDb, WeakDb}; use db_driver::{IdbDatabaseBuilder, IdbDatabaseImpl, IdbObjectStoreImpl, IdbTransactionImpl, OnUpgradeNeededCb}; -use indexed_cursor::{cursor_event_loop, CursorBuilder, CursorDriver, CursorError, CursorFilters, CursorResult, - DbCursorEventTx}; +use indexed_cursor::{cursor_event_loop, CursorBuilder, CursorDriver, CursorError, CursorFilters, CursorFiltersExt, + CursorResult, DbCursorEventTx}; type DbEventTx = mpsc::UnboundedSender; type DbTransactionEventTx = mpsc::UnboundedSender; @@ -654,11 +654,17 @@ impl<'transaction, Table: TableSignature> DbTable<'transaction, Table> { /// Opens a cursor by the specified `index`. /// https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/openCursor - async fn open_cursor(&self, index: &str, filters: CursorFilters) -> CursorResult { + async fn open_cursor( + &self, + index: &str, + filters: CursorFilters, + filters_ext: CursorFiltersExt, + ) -> CursorResult { let (result_tx, result_rx) = oneshot::channel(); let event = internal::DbTableEvent::OpenCursor { index: index.to_owned(), filters, + filters_ext, result_tx, }; let cursor_event_tx = send_event_recv_response(&self.event_tx, event, result_rx) @@ -741,9 +747,10 @@ async fn table_event_loop(mut rx: mpsc::UnboundedReceiver { - open_cursor(&table, index, filters, result_tx); + open_cursor(&table, index, filters, filters_ext, result_tx); }, } } @@ -778,6 +785,7 @@ fn open_cursor( table: &IdbObjectStoreImpl, index: String, filters: CursorFilters, + filter_ext: CursorFiltersExt, result_tx: oneshot::Sender>, ) { let db_index = match table.open_index(&index) { @@ -790,7 +798,7 @@ fn open_cursor( return; }, }; - let cursor = match CursorDriver::init_cursor(db_index, filters) { + let cursor = match CursorDriver::init_cursor(db_index, filters, filter_ext) { Ok(cursor) => cursor, Err(e) => { result_tx.send(Err(e)).ok(); @@ -899,6 +907,7 @@ mod internal { OpenCursor { index: String, filters: CursorFilters, + filters_ext: CursorFiltersExt, result_tx: oneshot::Sender>, }, }