Skip to main content

dittolive_ditto/store/query_builder/collection/
mod.rs

1//! Use [`ditto.store().collection("...")`] to access the [`Collection`] API.
2//!
3//! [`ditto.store().collection("...")`]: crate::store::Store::collection
4
5use_prelude!();
6
7// TODO(v5): Remove pub from mod
8/// ```
9/// use dittolive_ditto::store::collection::document::{DittoDocument, DittoMutDocument};
10/// ```
11#[doc(hidden)]
12pub mod document;
13use std::sync::Weak;
14
15pub use self::document::{DittoDocument, DittoMutDocument};
16
17// TODO(v5): remove
18/// ```
19/// use dittolive_ditto::store::collection::document_id::DocumentId;
20/// ```
21#[doc(hidden)]
22pub mod document_id {
23    // Re-exporting new location from old location to prevent hard breakage
24    pub use crate::store::DocumentId;
25}
26pub use self::document_id::DocumentId;
27
28// TODO(v5): Remove pub from mod
29/// ```
30/// use dittolive_ditto::store::collection::pending_cursor_operation::PendingCursorOperation;
31/// ```
32#[doc(hidden)]
33pub mod pending_cursor_operation;
34pub use self::pending_cursor_operation::PendingCursorOperation;
35
36// TODO(v5): Remove pub from mod
37/// ```
38/// use dittolive_ditto::store::collection::pending_id_specific_operation::PendingIdSpecificOperation;
39/// ```
40#[doc(hidden)]
41pub mod pending_id_specific_operation;
42pub use self::pending_id_specific_operation::PendingIdSpecificOperation;
43
44// TODO(v5): Remove pub from mod
45/// ```
46/// use dittolive_ditto::store::collection::type_traits::MutableValue;
47/// ```
48#[doc(hidden)]
49pub mod type_traits;
50pub use self::type_traits::MutableValue;
51use crate::{
52    error::{DittoError, ErrorKind},
53    store::attachment::{
54        DittoAttachment, DittoAttachmentFetchEvent, DittoAttachmentFetcher, DittoAttachmentToken,
55        FetcherVersion,
56    },
57};
58
59/// Use [`ditto.store().collection("...")?`] to query or mutate documents in a [`Collection`].
60///
61/// [`ditto.store().collection("...")?`]: crate::store::Store::collection
62#[derive(Clone, Debug)]
63pub struct Collection {
64    pub(crate) ditto: Weak<BoxedDitto>,
65    pub(crate) collection_name: char_p::Box,
66}
67
68impl Collection {
69    pub(crate) fn new(ditto: Weak<BoxedDitto>, collection_name: String) -> Self {
70        let collection_name = char_p::new(collection_name.as_str());
71        Self {
72            ditto,
73            collection_name,
74        }
75    }
76
77    /// Returns the name of this `Collection`.
78    ///
79    /// # Example
80    ///
81    /// ```
82    /// # use dittolive_ditto::prelude::*;
83    /// # fn example(ditto: &Ditto) -> anyhow::Result<()> {
84    /// let cars = ditto.store().collection("cars")?;
85    /// assert_eq!(cars.name(), "cars");
86    /// # Ok(())
87    /// # }
88    /// ```
89    pub fn name(&self) -> &str {
90        self.collection_name.as_ref().to_str()
91    }
92
93    /// Query all documents in the named collection.
94    ///
95    /// This produces a [`PendingCursorOperation`] which may be used to perform
96    /// subsequent operations on the queried documents, in this case all documents
97    /// in the named collection.
98    ///
99    /// # Example
100    ///
101    /// ```
102    /// use dittolive_ditto::prelude::*;
103    /// # fn example(ditto: &Ditto) -> anyhow::Result<()> {
104    /// let collection = ditto.store().collection("cars")?;
105    /// let pending_op = collection.find_all();
106    /// let documents: Vec<_> = pending_op.exec()?;
107    /// # Ok(())
108    /// # }
109    /// ```
110    pub fn find_all(&self) -> PendingCursorOperation<'_> {
111        self.find("true")
112    }
113
114    /// Queries documents in the named collection according to the given QueryBuilder query string.
115    ///
116    /// **Note: The query strings accepted here are _NOT_ Ditto-Query-Language (DQL) queries.
117    /// If you can achieve your use-case via a DQL query, we recommend you do that instead.
118    /// See [the DQL module docs] for more info about DQL**.
119    ///
120    /// This produces a [`PendingCursorOperation`] which may be used to perform
121    /// subsequent operations on the queried documents, in this case all documents
122    /// in the named collection.
123    ///
124    /// For more details about the query syntax accepted here, [see the `query_builder`
125    /// module documentation]
126    ///
127    /// [the DQL module docs]: crate::dql
128    /// [see the `query_builder` module documentation]: crate::store::query_builder#legacy-query-syntax
129    pub fn find(&self, query: &'_ str) -> PendingCursorOperation<'_> {
130        #[allow(deprecated)]
131        PendingCursorOperation::<'_>::new(
132            self.ditto.clone(),
133            self.collection_name.to_owned(),
134            query,
135            None,
136        )
137    }
138
139    /// Generates a [`PendingCursorOperation`] with the provided query and query arguments that can
140    /// be used to find the documents matching the query at a point in time or you can chain a call
141    /// to [`PendingCursorOperation::observe_local`] or [`PendingCursorOperation::subscribe`] if
142    /// you want to get updates about documents matching the query as they occur. It can also be
143    /// used to update, remove, or evict documents.
144    ///
145    /// This is the recommended function to use when performing queries on a collection if you have
146    /// any dynamic data included in the query string. It allows you to provide a query string with
147    /// placeholders, in the form of `$args.my_arg_name`, along with an accompanying dictionary of
148    /// arguments, in the form of `{ "my_arg_name": "some value" }`, and the placeholders will be
149    /// appropriately replaced by the matching provided arguments from the dictionary. This includes
150    /// handling things like wrapping strings in quotation marks and arrays in square brackets, for
151    /// example.
152    pub fn find_with_args<V: ::serde::Serialize, C: Borrow<V>>(
153        &self,
154        query: &'_ str,
155        query_args: C,
156    ) -> PendingCursorOperation<'_> {
157        #[allow(deprecated)]
158        PendingCursorOperation::<'_>::new(
159            self.ditto.clone(),
160            self.collection_name.to_owned(),
161            query,
162            Some(serde_cbor::to_vec(query_args.borrow()).unwrap()),
163        )
164    }
165
166    /// Generates a [`PendingIdSpecificOperation`] with the provided document Id that can be used
167    /// to find the document at a point in time or you can chain a call to
168    /// [`PendingIdSpecificOperation::observe_local`] or [`PendingIdSpecificOperation::subscribe`]
169    /// if you want to get updates about the document over time. It can also be used to update,
170    /// remove, or evict the document.
171    pub fn find_by_id(&self, doc_id: impl Into<DocumentId>) -> PendingIdSpecificOperation {
172        PendingIdSpecificOperation {
173            ditto: self.ditto.clone(),
174            collection_name: self.collection_name.to_owned(),
175            doc_id: doc_id.into(),
176        }
177    }
178}
179
180/// Convenience impl to avoid having to type `.clone()` everywhere.
181impl From<&DocumentId> for DocumentId {
182    fn from(doc: &DocumentId) -> Self {
183        doc.clone()
184    }
185}
186
187impl Collection {
188    /// Inserts a new document into the collection and returns its Id. If the
189    /// document already exists, the provided document content will be merged
190    /// with the existing document's content.
191    pub fn upsert<V: ::serde::Serialize, C: Borrow<V>>(
192        &self,
193        content: C,
194    ) -> Result<DocumentId, DittoError> {
195        self.insert_cbor(
196            ::serde_cbor::to_vec(content.borrow()).unwrap().as_slice(),
197            WriteStrategy::Merge,
198            None,
199        )
200    }
201
202    /// Inserts a new document into the collection and returns its Id. If the
203    /// document already exists, the behavior is determined by the provided
204    /// `write_strategy`.
205    pub fn upsert_with_strategy<V: ::serde::Serialize, C: Borrow<V>>(
206        &self,
207        content: C,
208        write_strategy: WriteStrategy,
209    ) -> Result<DocumentId, DittoError> {
210        self.insert_cbor(
211            ::serde_cbor::to_vec(content.borrow()).unwrap().as_slice(),
212            write_strategy,
213            None,
214        )
215    }
216
217    /// Inserts a new document into the collection and returns its assigned Id.
218    /// Use this method when the content has already been serialized externally
219    /// into a CBOR-formatted byte array
220    pub(crate) fn insert_cbor(
221        &self,
222        cbor: &'_ [u8],
223        write_strategy: WriteStrategy,
224        write_txn: Option<&'_ mut ffi_sdk::CWriteTransaction>,
225    ) -> Result<DocumentId, DittoError> {
226        let ditto = self
227            .ditto
228            .upgrade()
229            .ok_or(crate::error::ErrorKind::ReleasedDittoInstance)?;
230        let write_strategy_rs = write_strategy.as_write_strategy_rs();
231
232        let hint: Option<char_p::Ref<'_>> = None;
233        let id = {
234            ffi_sdk::ditto_collection_insert_value(
235                &*ditto,
236                self.collection_name.as_ref(),
237                cbor.into(),
238                write_strategy_rs,
239                hint,
240                write_txn,
241            )
242        }
243        .ok_or(ErrorKind::InvalidInput)?;
244
245        Ok(id.to::<Box<[u8]>>().into())
246    }
247
248    /// Creates a new attachment, which can then be inserted into a document.
249    ///
250    /// This API is deprecated. Use [`ditto.store().new_attachment()`][Store::new_attachment]
251    /// instead.
252    ///
253    /// The file residing at the provided path will be copied into the Ditto’s store. The
254    /// [`DittoAttachment`] object that is returned is what you can
255    /// then use to insert an attachment into a document.
256    ///
257    /// You can provide metadata about the attachment, which will be replicated to other peers
258    /// alongside the file attachment.
259    #[doc(hidden)]
260    #[deprecated = "Use `ditto.store().new_attachment()` instead"]
261    pub fn new_attachment<P: AsRef<Path>>(
262        &self,
263        filepath: P,
264        metadata: HashMap<String, String>,
265    ) -> Result<DittoAttachment, DittoError> {
266        let ditto = &self
267            .ditto
268            .upgrade()
269            .ok_or(crate::error::ErrorKind::ReleasedDittoInstance)?;
270        DittoAttachment::from_file_and_metadata(&filepath, metadata, ditto)
271    }
272
273    /// Fetch the attachment corresponding to the provided attachment token.
274    ///
275    /// This API is deprecated. Use [`ditto.store().fetch_attachment()`][Store::fetch_attachment]
276    /// instead.
277    ///
278    /// - `on_fetch_event`: A closure that will be called when the status of the request to fetch
279    ///   the attachment has changed. If the attachment is already available then this will be
280    ///   called almost immediately with a completed status value.
281    #[doc(hidden)]
282    #[deprecated = "Use `ditto.store().fetch_attachment()` instead"]
283    pub fn fetch_attachment<'a>(
284        &self,
285        attachment_token: DittoAttachmentToken,
286        on_fetch_event: impl 'a + Send + Sync + Fn(DittoAttachmentFetchEvent),
287    ) -> Result<DittoAttachmentFetcher<'a, FetcherVersion::V1>, DittoError> {
288        let ditto = self
289            .ditto
290            .upgrade()
291            .ok_or(crate::error::ErrorKind::ReleasedDittoInstance)?;
292        DittoAttachmentFetcher::new(attachment_token, None, &ditto, move |ev, _| {
293            on_fetch_event(ev)
294        })
295    }
296}