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}