dittolive_ditto/store/
mod.rs

1//! Use [`ditto.store()`] to access the [`Store`] API to read, write, and remove documents.
2//!
3//! The `Store` provides two interfaces for interacting with data: Ditto Query Language
4//! (DQL), and the legacy "Query Builder" API. Where possible, we recommend developing new
5//! functionality using DQL, as we will eventually deprecate Query Builder.
6//!
7//! - [See the `dql` module docs for examples of DQL queries in action][0]
8//!
9//! [`ditto.store()`]: crate::prelude::Ditto::store
10//! [0]: crate::dql
11
12use_prelude!();
13
14use std::{
15    ops::Deref,
16    sync::{
17        atomic::{self, AtomicU64},
18        RwLock, Weak,
19    },
20};
21
22use ffi_sdk::FfiStoreObserver;
23
24use crate::{debug, error};
25
26pub mod attachment;
27mod document_id;
28mod observer;
29pub mod transactions;
30
31use self::attachment::{DittoAttachmentFetcher, FetcherVersion};
32pub use self::{
33    document_id::DocumentId,
34    observer::{ChangeHandler, ChangeHandlerWithSignalNext, SignalNext, StoreObserver},
35};
36use crate::{
37    ditto::DittoFields,
38    dql::{query::IntoQuery, *},
39    error::{DittoError, ErrorKind},
40    utils::{extension_traits::FfiResultIntoRustResult, SetArc},
41};
42
43type CancelToken = u64;
44
45/// Use [`ditto.store()`] to access the [`Store`] API to read, write, and remove documents.
46///
47/// [See the `store` module for guide-level docs and examples][0].
48///
49/// [`ditto.store()`]: crate::prelude::Ditto::store
50/// [0]: crate::store
51#[derive(Clone)]
52pub struct Store {
53    ditto: Arc<ffi_sdk::BoxedDitto>,
54    // FIXME(Daniel): unify this field with `.ditto`
55    weak_ditto_fields: Weak<DittoFields>,
56    #[allow(clippy::type_complexity)]
57    attachment_fetchers: Arc<
58        RwLock<HashMap<CancelToken, (bool, DittoAttachmentFetcher<'static, FetcherVersion::V2>)>>,
59    >,
60}
61
62impl Store {
63    pub(crate) fn new(
64        ditto: Arc<ffi_sdk::BoxedDitto>,
65        weak_ditto_fields: Weak<DittoFields>,
66    ) -> Self {
67        Self {
68            ditto,
69            weak_ditto_fields,
70            attachment_fetchers: <_>::default(),
71        }
72    }
73
74    /// Installs and returns a store observer for a query, configuring Ditto to
75    /// trigger the passed in change handler whenever documents in the local
76    /// store change such that the result of the matching query changes. The
77    /// passed in query must be a `SELECT` query, otherwise an error will be
78    /// returned.
79    ///
80    /// # Example
81    ///
82    /// ```
83    /// use dittolive_ditto::prelude::*;
84    /// # fn main() -> anyhow::Result<()> {
85    /// # let (_root, ditto) = dittolive_ditto::doctest_helpers::doctest_ditto();
86    ///
87    /// let store = ditto.store();
88    /// let _observer = store.register_observer(
89    ///     "SELECT * FROM cars WHERE color = 'blue'",
90    ///     move |query_result| {
91    ///         for item in query_result.iter() {
92    ///             // ... handle each item
93    ///         }
94    ///     },
95    /// )?;
96    /// # Ok(())
97    /// # }
98    /// ```
99    ///
100    /// The first invocation of the change handler will always happen after
101    /// this method has returned.
102    ///
103    /// The observer will remain active until:
104    ///
105    /// - the [`StoreObserver`] handle gets dropped,
106    /// - the [`observer.cancel()`] method is called, or
107    /// - the owning [`Ditto`] instance has shut down
108    ///
109    /// Observer callbacks will never be called concurrently. That is, one callback
110    /// must return before the observer will call the handler again. See
111    /// [`ditto.store().register_observer_with_signal_next(...)`] if you want
112    /// to manually signal readiness for the next callback.
113    ///
114    /// [`ditto.store().register_observer_with_signal_next(...)`]: crate::store::Store::register_observer_with_signal_next
115    /// [`observer.cancel()`]: crate::store::StoreObserver::cancel
116    /// [`Ditto`]: crate::prelude::Ditto
117    pub fn register_observer<Q, F>(
118        &self,
119        query: Q,
120        on_change: F,
121    ) -> Result<Arc<StoreObserver>, DittoError>
122    where
123        Q: IntoQuery,
124        Q::Args: Serialize,
125        F: ChangeHandler,
126    {
127        let ditto = Ditto::upgrade(&self.weak_ditto_fields)?;
128        let query = query.into_query()?;
129
130        let observer = Arc::new(StoreObserver::new(
131            &ditto,
132            &query.string,
133            query.args_cbor.as_deref(),
134            on_change,
135        )?);
136        Ok(observer)
137    }
138
139    /// Installs and returns a store observer for a query, configuring Ditto to
140    /// trigger the passed in change handler whenever documents in the local
141    /// store change such that the result of the matching query changes. The
142    /// passed in query must be a `SELECT` query, otherwise an error will be
143    /// returned.
144    ///
145    /// Here, a function is passed as an additional argument to the change
146    /// handler. Call it when the handler is ready for another callback. See
147    /// [`register_observer`] for a convenience method that automatically
148    /// signals the next invocation.
149    ///
150    /// # Example
151    ///
152    /// ```
153    /// use dittolive_ditto::prelude::*;
154    /// # fn main() -> anyhow::Result<()> {
155    /// # let (_root, ditto) = dittolive_ditto::doctest_helpers::doctest_ditto();
156    ///
157    /// let store = ditto.store();
158    /// let _observer = store.register_observer_with_signal_next(
159    ///     "SELECT * FROM cars WHERE color = 'blue'",
160    ///     move |query_result, signal_next| {
161    ///         for item in query_result.iter() {
162    ///             // ... handle each item
163    ///         }
164    ///
165    ///         // Call `signal_next` when you're ready for the next callback
166    ///         signal_next();
167    ///     },
168    /// )?;
169    /// # Ok(())
170    /// # }
171    /// ```
172    ///
173    /// The first invocation of the change handler will always happen after
174    /// this method has returned.
175    ///
176    /// The observer will remain active until:
177    ///
178    /// - the [`StoreObserver`] handle gets dropped,
179    /// - the [`StoreObserver::cancel`] method is called, or
180    /// - the owning [`Ditto`] instance has shut down
181    ///
182    /// After invoking the callback once, the observer will wait to deliver
183    /// another callback until after you've called [`signal_next`]. See [`SignalNext`] for
184    /// how Ditto handles backpressure and pending updates while callback delivery is paused.
185    ///
186    /// [`register_observer`]: Store::register_observer
187    /// [`signal_next`]: crate::store::SignalNext
188    pub fn register_observer_with_signal_next<Q, F>(
189        &self,
190        query: Q,
191        on_change: F,
192    ) -> Result<Arc<StoreObserver>, DittoError>
193    where
194        Q: IntoQuery,
195        Q::Args: Serialize,
196        F: ChangeHandlerWithSignalNext,
197    {
198        let ditto = Ditto::upgrade(&self.weak_ditto_fields)?;
199        let query = query.into_query()?;
200
201        let new_obs = Arc::new(StoreObserver::with_signal_next(
202            &ditto,
203            &query.string,
204            query.args_cbor.as_deref(),
205            on_change,
206        )?);
207        Ok(new_obs)
208    }
209
210    /// Gets temporary access to the set of currently registered observers.
211    ///
212    /// A (read) lock is held until the return value is dropped: this means
213    /// that neither [`Self::register_observer()`] nor
214    /// [`StoreObserver::cancel()`] can make progress until this read
215    /// lock is released.
216    pub fn observers(&self) -> impl '_ + Deref<Target = SetArc<StoreObserver>> {
217        let observers: repr_c::Vec<repr_c::Box<FfiStoreObserver>> =
218            ffi_sdk::dittoffi_store_observers(&self.ditto);
219        let observers: Vec<_> = observers.into();
220        let observers = observers
221            .into_iter()
222            .map(|handle: repr_c::Box<FfiStoreObserver>| Arc::new(StoreObserver { handle }))
223            .collect::<SetArc<_>>();
224
225        Box::new(observers)
226    }
227
228    /// Executes the given query in the local store and returns the result.
229    ///
230    /// # Example
231    ///
232    /// ```
233    /// use dittolive_ditto::prelude::*;
234    /// use dittolive_ditto::dql::QueryResult;
235    /// # #[tokio::main]
236    /// # async fn main() -> anyhow::Result<()> {
237    /// # let (_root, ditto) = dittolive_ditto::doctest_helpers::doctest_ditto();
238    ///
239    /// // Query a collection
240    /// let result = ditto.store().execute("SELECT * FROM cars").await?;
241    ///
242    /// // Insert a document into a collection
243    /// let insert_result: QueryResult = ditto
244    ///     .store()
245    ///     .execute((
246    ///          "INSERT INTO cars DOCUMENTS (:newCar)",
247    ///          serde_json::json!({
248    ///              "newCar": {
249    ///                  "make": "ford",
250    ///                  "color": "blue"
251    ///              }
252    ///          })
253    ///     ))
254    ///     .await?;
255    /// # Ok(())
256    /// # }
257    /// ```
258    ///
259    /// Use placeholders to incorporate values from the optional `query_args`
260    /// parameter into the query. The keys of the query arguments object must
261    /// match the placeholders used within the query. You can not use placeholders
262    /// in the `FROM` clause.
263    ///
264    /// This method only returns results from the local store without waiting for any
265    /// [`SyncSubscription`]s to have caught up with the
266    /// latest changes. Only use this method if your program must proceed with immediate results.
267    ///
268    /// Use [`ditto.store().register_observer(...)`] to receive updates to query results
269    /// as soon as they have been synced to this peer.
270    ///
271    /// ## Query parameter
272    ///
273    /// The `query` parameter must implement [`IntoQuery`], which is a trait that is implemented by
274    /// objects that can be turned into a query string along with the relevant query arguments.
275    ///
276    /// For queries with no arguments, a [`String`] is sufficient.
277    ///
278    /// [`SyncSubscription`]: crate::sync::SyncSubscription
279    /// [`ditto.store().register_observer(...)`]: crate::store::Store::register_observer
280    pub async fn execute<Q>(&self, query: Q) -> Result<QueryResult, DittoError>
281    where
282        Q: IntoQuery,
283        Q::Args: serde::Serialize,
284    {
285        let query = query.into_query()?;
286        let query_string = (&*query.string).into();
287        let query_args = query.args_cbor.as_deref().map(Into::into);
288
289        let ffi_query_result =
290            ffi_sdk::dittoffi_try_exec_statement(&self.ditto, query_string, query_args)
291                .into_rust_result()?;
292
293        Ok(QueryResult::from(ffi_query_result))
294    }
295
296    /// Creates a new attachment, which can then be inserted into a document.
297    ///
298    /// The file residing at the provided path will be copied into Ditto’s store. The
299    /// [`DittoAttachment`] object that is returned is what you can
300    /// then use to insert an attachment into a document.
301    ///
302    /// You can provide custom user data about the attachment, which will be replicated to other
303    /// peers alongside the file attachment.
304    pub async fn new_attachment(
305        &self,
306        filepath: &(impl ?Sized + AsRef<Path>),
307        user_data: HashMap<String, String>,
308    ) -> Result<DittoAttachment, DittoError> {
309        DittoAttachment::from_file_and_metadata(filepath, user_data, &self.ditto)
310    }
311
312    /// Creates a new attachment from in-memory data
313    ///
314    /// Refer to [`new_attachment`](Self::new_attachment) for additional information.
315    pub async fn new_attachment_from_bytes(
316        &self,
317        bytes: &(impl ?Sized + AsRef<[u8]>),
318        user_data: HashMap<String, String>,
319    ) -> Result<DittoAttachment, DittoError> {
320        DittoAttachment::from_bytes_and_metadata(bytes, user_data, &self.ditto)
321    }
322
323    /// Fetches the attachment corresponding to the provided attachment token.
324    /// - `attachment_token`: can be either a [`DittoAttachmentToken`], or a `&BTreeMap<CborValue,
325    ///   CborValue>`, that is, the output of a [`QueryResultItem::value()`] once casted
326    ///   [`.as_object()`][crate::prelude::CborValueGetters::as_object()].
327    ///
328    /// - `on_fetch_event`: A closure that will be called when the status of the request to fetch
329    ///   the attachment has changed. If the attachment is already available then this will be
330    ///   called almost immediately with a completed status value.
331    ///
332    /// The returned [`DittoAttachmentFetcher`] is a handle which is safe to discard, unless you
333    /// wish to be able to [`.cancel()`][DittoAttachmentFetcher::cancel] the fetching operation.
334    /// When not explicitly cancelled, the fetching operation will remain active until it either
335    /// completes, the attachment is deleted, or the owning [`Ditto`] object is dropped.
336    pub fn fetch_attachment(
337        &self,
338        attachment_token: impl attachment::DittoAttachmentTokenLike,
339        on_fetch_event: impl 'static + Send + Sync + Fn(DittoAttachmentFetchEvent),
340    ) -> Result<DittoAttachmentFetcher<'static, FetcherVersion::V2>, DittoError> {
341        let attachment_token = attachment_token.parse_attachment_token()?;
342
343        let weak_ditto = self.weak_ditto_fields.clone();
344        let ditto = weak_ditto
345            .upgrade()
346            .ok_or(ErrorKind::ReleasedDittoInstance)?;
347
348        let mut attachment_fetchers_lockguard = self.attachment_fetchers.write().unwrap();
349        let fetcher = DittoAttachmentFetcher::new(
350            attachment_token,
351            Some(&ditto),
352            &self.ditto,
353            // Shim around `on_fetch_event` to `cancel` on completion.
354            move |event, cancel_token: &AtomicU64| {
355                let has_finished = matches! {
356                    event,
357                    | DittoAttachmentFetchEvent::Completed { .. }
358                    | DittoAttachmentFetchEvent::Deleted
359                };
360                on_fetch_event(event);
361                if has_finished {
362                    if let Some(ditto) = weak_ditto.upgrade() {
363                        let mut attachment_fetchers_inner_lockguard =
364                            ditto.store.attachment_fetchers.write().unwrap();
365                        // Relaxed is fine thanks to the lock.
366                        let cancel_token = cancel_token.load(atomic::Ordering::Relaxed);
367                        ditto.store.unregister_fetcher(
368                            cancel_token,
369                            Some(&mut *attachment_fetchers_inner_lockguard),
370                        );
371                    }
372                }
373            },
374        )?;
375        let (cancel_token, was_zero) = fetcher.cancel_token_ensure_unique();
376        attachment_fetchers_lockguard.insert(cancel_token, (was_zero, fetcher.clone()));
377        Ok(fetcher)
378    }
379
380    fn unregister_fetcher(
381        &self,
382        mut fetcher_cancel_token: CancelToken,
383        fetchers: Option<
384            &mut HashMap<CancelToken, (bool, DittoAttachmentFetcher<'static, FetcherVersion::V2>)>,
385        >,
386    ) -> bool {
387        let mut lock_guard = None;
388        let fetchers = fetchers.unwrap_or_else(|| {
389            &mut **lock_guard.get_or_insert(self.attachment_fetchers.write().unwrap())
390        });
391
392        let Some((was_zero, removed_fetcher)) = fetchers.remove(&fetcher_cancel_token) else {
393            return false;
394        };
395        drop(lock_guard);
396
397        if was_zero {
398            fetcher_cancel_token = 0;
399        }
400
401        let att_token = &removed_fetcher.context.token;
402
403        #[allow(deprecated)] // Workaround for patched tracing
404        {
405            debug!(
406                token_id = %att_token.id(),
407                %fetcher_cancel_token,
408                "unregistering ditto attachment fetcher"
409            );
410        }
411
412        let status = ffi_sdk::ditto_cancel_resolve_attachment(
413            &self.ditto,
414            att_token.id.as_ref().into(),
415            fetcher_cancel_token,
416        );
417
418        if status != 0 {
419            #[allow(deprecated)] // Workaround for patched tracing
420            {
421                error!(
422                    token_id = %att_token.id(),
423                    %fetcher_cancel_token,
424                    "failed to clean up attachment fetcher"
425                );
426            }
427        }
428        status == 0
429    }
430
431    /// Gets a copy of the set of currently registered attachment fetchers.
432    ///
433    /// A (read) lock is held during the copy: this contends with [`Self::fetch_attachment()`] and
434    /// with [`DittoAttachmentFetcher::cancel()`].
435    pub fn attachment_fetchers(&self) -> Vec<DittoAttachmentFetcher<'static, FetcherVersion::V2>> {
436        self.attachment_fetchers
437            .read()
438            .unwrap()
439            .iter()
440            .map(|(_, (_, fetcher))| fetcher.clone())
441            .collect()
442    }
443}
444
445/// Specify the order of returned Documents in a query.
446#[deprecated(since = "5.1.0", note = "Use DQL `ORDER BY` clauses instead.")]
447#[non_exhaustive]
448#[derive(Clone, Copy, PartialEq, Eq, Debug)]
449pub enum SortDirection {
450    /// First result is "smallest", last result is "largest"
451    Ascending,
452
453    /// First result is "largest", last result is "smallest"
454    Descending,
455}