dittolive_ditto/store/query_builder/mod.rs
1//! The original data API for Ditto uses a builder syntax to execute queries.
2//!
3//! **NOTE: If you're newly adopting Ditto into your project, we recommend you
4//! use the newer [Ditto Query Language (DQL)] API for interacting with Ditto data**.
5//!
6//! The QueryBuilder API provides method calls that may be chained together
7//! in order to query or mutate data in the Ditto store, as well as to subscribe
8//! to changes in data on another device in the Ditto mesh.
9//!
10//! Here's the general flow for querying documents using the QueryBuilder API:
11//!
12//! - Use [`ditto.store().collection("...")?`] and provide a collection name to receive a
13//! [`Collection`] handle for querying documents in that collection.
14//!
15//! - Then, use one of the "find" methods such as [`.find_all()`], [`.find_by_id(...)`],
16//! [`.find(...)`], or [`.find_with_args(...)`] to create a [`PendingCursorOperation`] which will
17//! yield documents from the collection. This cursor object may then be used to do any of the
18//! following:
19//!
20//! - Use [`.exec()`] to execute the query immediately, returning a list of all documents matching
21//! the query.
22//!
23//! - Use [`.subscribe()`] to create a [`Subscription`] which syncs documents matching the query
24//! from other Ditto peers. The subscription will remain active until it's dropped.
25//!
26//! - Use [`.observe_local(...)`] to register a callback which will be called any time documents
27//! matching the query are changed _in the local store_. Only when combined with an active
28//! [`Subscription`] will this callback fire when documents are changed on a remote peer.
29//!
30//! ## Example: Query all documents in a collection
31//!
32//! ```
33//! use dittolive_ditto::prelude::*;
34//!
35//! # fn example(ditto: &Ditto) -> anyhow::Result<()> {
36//! let documents = ditto.store().collection("cars")?.find_all().exec()?;
37//! let typed_documents: Vec<serde_json::Value> = documents
38//! .into_iter()
39//! .flat_map(|doc| doc.typed::<serde_json::Value>().ok())
40//! .collect();
41//! println!("Documents in 'cars': {typed_documents:?}");
42//! # Ok(())
43//! # }
44//! ```
45//!
46//! ## Example: Subscribe to and observe changes from peers
47//!
48//! Use [`.observe_local(...)`] to register a callback _before_ using
49//! [`.subscribe()`] to create a [`Subscription`] to ensure that all
50//! documents synced from a remote peer are seen by the observer callback:
51//!
52//! ```
53//! use dittolive_ditto::prelude::*;
54//!
55//! # fn example(ditto: &Ditto) -> anyhow::Result<()> {
56//! let cars = ditto.store().collection("cars")?;
57//! let query = cars.find_all();
58//!
59//! // Create a `LiveQuery` by registering a local observer
60//! // This observer is called whenever documents matching the query
61//! // are changed in this peer's local store.
62//! //
63//! // Dropping this handle will cancel the observer
64//! let _live_query = query.observe_local(|docs, _event| {
65//! let typed_docs: Vec<serde_json::Value> = docs
66//! .into_iter()
67//! .flat_map(|doc| doc.typed::<serde_json::Value>().ok())
68//! .collect();
69//! println!("Observed updated document: {typed_docs:?}");
70//! })?;
71//!
72//! // Create a `Subscription` which syncs documents from other peers
73//! //
74//! // Dropping this handle will cancel the subscription
75//! let _subscription = query.subscribe();
76//! # Ok(())
77//! # }
78//! ```
79//!
80//! # Ditto CRDT Types
81//!
82//! The Ditto CRDT types are [`DittoCounter`], [`DittoMutableCounter`], [`DittoRegister`],
83//! and [`DittoMutableRegister`]. These types have dedicated CRDT logic and specific behavior
84//! for merging concurrent updates. The mutable variants allow for updating the CRDT values,
85//! whereas the non-mutable variants are read-only.
86//!
87//! To obtain a read-only CRDT, use the [`.get::<T>("...")`] API of [`DittoDocument`].
88//! To obtain a mutable CRDT, use the [`.get_mut::<T>("...")`] API of [`DittoMutDocument`].
89//! Both of these traits are implemented for any type that `Deref`s to `Document`, such
90//! as the [`BoxedDocument`]s returned by [`.exec()`].
91//!
92//! [`.get::<T>("...")`]: DittoDocument::get
93//! [`.get_mut::<T>("...")`]: DittoMutDocument::get_mut
94//! [`BoxedDocument`]: crate::prelude::BoxedDocument
95//! [`.exec()`]: crate::store::query_builder::PendingCursorOperation::exec
96//!
97//! ## Counter
98//!
99//! The [`DittoCounter`] and [`DittoMutableCounter`] represent counters which can be updated
100//! simultaneously on several peers at once without losing concurrent increments.
101//!
102//! ### Example
103//!
104//! ```
105//! use dittolive_ditto::prelude::*;
106//!
107//! # fn example(ditto: &Ditto) -> anyhow::Result<()> {
108//! let collection = ditto.store().collection("foo")?;
109//!
110//! // Insert a new document with a field "counter" containing
111//! // a `DittoCounter` with default value 0
112//! let id = collection.upsert(serde_json::json!({
113//! "counter": DittoCounter::new()
114//! }))?;
115//!
116//! // Find the document by its ID and `.get` the "counter" field.
117//! let doc = collection.find_by_id(&id).exec()?;
118//! let counter: DittoCounter = doc.get("counter")?;
119//! assert_eq!(counter.value(), 0.0);
120//!
121//! // Update the document by ID and increment "counter" as a DittoMutableCounter
122//! collection.find_by_id(&id).update(|maybe_doc| {
123//! let doc = maybe_doc.unwrap();
124//! let mut counter = doc.get_mut::<DittoMutableCounter>("counter").unwrap();
125//! counter.increment(5.0).unwrap();
126//! })?;
127//!
128//! let counter: DittoCounter = doc.get("counter")?;
129//! assert_eq!(counter.value(), 5.0);
130//! # Ok(())
131//! # }
132//! ```
133//!
134//! ## Register
135//!
136//! [`DittoRegister`] and [`DittoMutableRegister`] are containers for a single value following
137//! the concept of LWW: "Last Write Wins". Primitive types such as `String`, `u32`, `bool`, etc.,
138//! are implicitly wrapped to and unwrapped from registers.
139//!
140//! ### Example: Primitives and Registers
141//!
142//! ```
143//! use dittolive_ditto::prelude::*;
144//!
145//! # fn example(ditto: &Ditto) -> anyhow::Result<()> {
146//! let collection = ditto.store().collection("register_tests")?;
147//!
148//! // Insert a new document with "register" and "string" fields with these values:
149//! let id = collection.upsert(serde_json::json!({
150//! "register": DittoRegister::new(42)?,
151//! "string": "SomeString"
152//! }))?;
153//!
154//! // Find the document by its ID and `.get` the "register" field
155//! let doc = collection.find_by_id(&id).exec()?;
156//! let register: DittoRegister = doc.get("register")?;
157//! assert_eq!(register.value::<u32>()?, 42);
158//!
159//! // Use a register to access a primitive type such as String
160//! let register: DittoRegister = doc.get("string")?;
161//! assert_eq!(register.value::<String>()?, "SomeString");
162//!
163//! // Update the "register" field by setting it directly to a value
164//! collection
165//! .find_by_id(&id)
166//! .update(|maybe_doc| {
167//! let doc = maybe_doc.unwrap();
168//! let mut register = doc.get_mut::<DittoMutableRegister>("register").unwrap();
169//! register.set(5.0).unwrap();
170//! })?;
171//!
172//! // Read back the "register" field and observe the changed value
173//! let register: DittoRegister = doc.get("register")?;
174//! assert_eq!(register.value::<f32>()?, 5.0);
175//!
176//! // Read the primitive value directly via `.get`
177//! let direct_access: f32 = doc.get("register")?;
178//! assert_eq!(direct_access, 5.0);
179//! # Ok(())
180//! # }
181//! ```
182//!
183//! ### Example: Mixed fields without Registers
184//!
185//! The main purpose of the [`DittoRegister`] type is to treat structs as a single CRDT value.
186//! Without Register, fields can be merged together and get mixed up. Consider this example
187//! where we would like to replace a Car vehicle and all of its fields with a Boat vehicle and
188//! _its_ fields:
189//!
190//! ```
191//! use dittolive_ditto::prelude::*;
192//! use serde::{Deserialize, Serialize};
193//!
194//! #[derive(Serialize, Deserialize)]
195//! struct Car {
196//! color: String,
197//! price: u32,
198//! }
199//! #[derive(Serialize, Deserialize)]
200//! struct Boat {
201//! length: f32,
202//! name: String,
203//! }
204//!
205//! # fn example(ditto: &Ditto) -> anyhow::Result<()> {
206//! let collection = ditto.store().collection("vehicles")?;
207//!
208//! // Insert a car as a new document, returning the assigned ID
209//! let car = Car {
210//! color: String::from("red"),
211//! price: 42_000,
212//! };
213//! let id = collection.upsert(serde_json::json!({ "vehicle": car }))?;
214//!
215//! // Now, attempt to replace the Car with a Boat using `.update`
216//! collection
217//! .find_by_id(&id)
218//! .update(|maybe_doc| {
219//! let doc = maybe_doc.unwrap();
220//!
221//! // Attempt to replace the value of the "vehicle" field with a Boat
222//! let boat = Boat {
223//! length: 248.0,
224//! name: String::from("Richelieu"),
225//! };
226//! doc.set("vehicle", boat).unwrap();
227//! })?;
228//!
229//! // Uh oh, instead of replacing the vehicle, we just merged fields into it!
230//! let document = collection.find_by_id(&id).exec()?;
231//! let typed = document.typed::<serde_json::Value>()?;
232//! println!("Vehicle document: {typed:#}");
233//! // Vehicle document: {
234//! // "vehicle": {
235//! // "color": "red",
236//! // "length": 248,
237//! // "name": "Richelieu",
238//! // "price": 42_000
239//! // }
240//! // }
241//! # Ok(())
242//! # }
243//! ```
244//!
245//! To solve this problem where fields get mixed together, we can use a [`DittoRegister`],
246//! which treats a block of fields as a single unbreakable unit. In this scenario, the
247//! original Car value will be lost, but the Boat value will successfully replace it.
248//!
249//! ```
250//! use dittolive_ditto::prelude::*;
251//! use serde::{Serialize, Deserialize};
252//!
253//! #[derive(Serialize, Deserialize)]
254//! struct Car {
255//! color : String,
256//! price: u32,
257//! }
258//!
259//! #[derive(Serialize, Deserialize)]
260//! struct Boat {
261//! length : f32,
262//! name: String,
263//! }
264//!
265//! # fn example(ditto: &Ditto) -> anyhow::Result<()> {
266//! let collection = ditto.store().collection("vehicles")?;
267//!
268//! // Insert the Car wrapped in a `DittoRegister`
269//! let car = Car { color: "red".to_string(), price: 42_000 };
270//! let id = collection.upsert(serde_json::json!({
271//! "vehicle": DittoRegister::new(car)?
272//! }))?;
273//!
274//! // Update the "vehicle" field, replacing the Car with a Boat
275//! collection
276//! .find_by_id(&id)
277//! .update(|maybe_doc| {
278//! let doc = maybe_doc.unwrap();
279//!
280//! let boat = Boat { length: 248.0, name: "Richelieu".to_string() };
281//! let mut register = doc.get_mut::<DittoMutableRegister>("vehicle").unwrap();
282//! register.set(boat).unwrap();
283//! })?;
284//!
285//! // Now, the fields in "vehicle" have been replaced by Boat rather than merged with Car!
286//! let document = collection.find_by_id(&id).exec()?;
287//! let typed = document.typed::<serde_json::Value>()?;
288//! println!("Vehicle document: {typed:#}");
289//! // Vechicle document: {
290//! // "vehicle": {
291//! // "length": 248,
292//! // "name" : "Richelieu"
293//! // }
294//! // }
295//! # Ok(())
296//! # }
297//! ```
298//!
299//! # Legacy Query Syntax
300//!
301//! Certain QueryBuilder APIs accept query strings, and it's important to note
302//! that these APIs DO NOT accept DQL, but rather a legacy query syntax. The
303//! following APIs use the legacy query syntax described in this section:
304//!
305//! - [`ditto.store().collection("...")?.find("<legacy query>")`]
306//! - [`ditto.store().collection("...")?.find_with_args("<legacy query>", ...)`]
307//!
308//! [`ditto.store().collection("...")?.find("<legacy query>")`]: crate::store::query_builder::Collection::find
309//! [`ditto.store().collection("...")?.find_with_args("<legacy query>", ...)`]: crate::store::query_builder::Collection::find_with_args
310//!
311//! For a full overview of the Legacy Query Syntax, [see the official Ditto
312//! docs Query Syntax page][0]. We'll briefly summarize the same info here.
313//!
314//! ## Boolean and Comparison operators
315//!
316//! The objective of the query syntax is to select a subset of documents in a
317//! collection. Here are some examples of operators that may be used to filter
318//! documents based on fields they contain and the values of those fields:
319//!
320//! - Equality (`==`): Check that a field matches a specific value:
321//! - `"isDeleted == true"`
322//! - `"isDeleted == false"`
323//! - `"title == 'Harry Potter'"`
324//!
325//! - Inequality (`!=`): Check that a field _does not_ match a specific value:
326//! - `"title != 'Lord of the Rings'"`
327//!
328//! - Less-than (`<`), Greater-than (`>`), Less-or-equal (`<=`), Greater-or-equal (`>=`):
329//! - `"age < 18"`
330//! - `"age <= 18"`
331//! - `"age > 18"`
332//! - `"age >= 18"`
333//!
334//! ## Compound Operators
335//!
336//! Compound operators allow you to add multiple conditions into a single query.
337//!
338//! - Logical AND (`&&`): Evaluates to `true` when both child conditions are true:
339//! - `"theme == 'Dark' && name == 'Light'"`
340//!
341//! - Logical OR (`||`): Evaluates to `true` when at least one child condition is true:
342//! - `"name == 'Tom' || name == 'Arthur'"`
343//!
344//! - Logical NOT (`!`): Inverts the condition:
345//! - `"!(name == 'Hamilton' || name == 'Morten')"`
346//!
347//! ## String Operations
348//!
349//! The legacy query syntax supports a handful of functions that may be used
350//! to do string comparisons against document field values.
351//!
352//! - Use `starts_with(field, test)` to test if the field begins with the test string
353//! - `"starts_with(title, 'Lord')"`
354//!
355//! - Use `ends_with(field, test)` to test if the field ends with the test string
356//! - `"ends_with(title, 'Rings')"`
357//!
358//! - Use `regex(field, '<regex>')` to test if a field matches the provided regular expression
359//! - `"regex(title, '^([A-Za-z]|[0-9]|_)+$')"`
360//!
361//! ## NULL values
362//!
363//! Use the `null` primitive to check for the existence of a value at a given field
364//!
365//! - To find documents with a `color` property that has no value:
366//! - `"color == null"`
367//!
368//! ## Array Operations
369//!
370//! When handling collections of data that different peers may make concurrent
371//! updates to, first consider using an embedded map in your documents. If this
372//! doesn't work for your use-case, then try using an array.
373//!
374//! The `array` type in Ditto is a CRDT and behaves differently than a typical
375//! `array` type. For more information, see the [Platform Manual on Data Types][1].
376//!
377//! > We recommend avoiding `array`s in Ditto where possible.
378//! >
379//! > Due to Ditto's use of CRDTs, arrays are much less graceful than maps
380//! > when syncing with other peers and merging concurrent updates.
381//!
382//! - Use `contains(array, value)` to check if the value exists in the array
383//! - `"contains(['blue', 'green'], color)"`
384//!
385//! ## Date and Time formats
386//!
387//! When querying or parsing date and time strings, use the [ISO-8601] standard
388//! format.
389//!
390//! - `"created_at >= '2022-04-29T00:55:31.859Z'"`
391//!
392//! ## Field Path Navigation
393//!
394//! If a field name consists of alphanumeric characters or includes underscores,
395//! use any of the following notations to navigate document properties:
396//!
397//! - For fields that are alphanumeric or include underscores, use dot notation:
398//! - `"name.last == 'Turing'"`
399//!
400//! - For all other scenarios, use bracket notation:
401//! - `"work['street-line'] == '678 Johnson Street'"`
402//!
403//! [Ditto Query Language (DQL)]: crate::dql
404//! [`ditto.store().collection("...")?`]: crate::store::Store::collection
405//! [`.find_all()`]: crate::store::query_builder::Collection::find_all
406//! [`.find_by_id(...)`]: crate::store::query_builder::Collection::find_by_id
407//! [`.find(...)`]: crate::store::query_builder::Collection::find
408//! [`.find_with_args(...)`]: crate::store::query_builder::Collection::find_with_args
409//! [`.exec()`]: crate::store::query_builder::PendingCursorOperation::exec
410//! [`.subscribe()`]: crate::store::query_builder::PendingCursorOperation::subscribe
411//! [`.observe_local(...)`]: crate::store::query_builder::PendingCursorOperation::observe_local
412//! [0]: https://docs.ditto.live/v4-4/FtP61n4tNyma6t9k-1WFW
413//! [1]: https://docs.ditto.live/v4-4/tAWkWoXz6TuRtoc-1VFVI
414//! [ISO-8601]: https://en.wikipedia.org/wiki/ISO_8601
415
416#[doc(hidden)]
417pub mod batch;
418#[doc(hidden)]
419pub mod collection;
420#[doc(hidden)]
421pub mod collections;
422#[doc(hidden)]
423pub mod live_query;
424#[doc(hidden)]
425pub mod subscription;
426#[doc(hidden)]
427pub mod types;
428#[doc(hidden)]
429pub mod update;
430
431/// Use [`pending_op.sort(...)`] to specify queried document ordering.
432///
433/// [`pending_op.sort(...)`]: PendingCursorOperation::sort
434pub use ffi_sdk::COrderByParam;
435/// Whether sorting should be done in ascending or descending order.
436pub use ffi_sdk::QuerySortDirection;
437
438pub use self::{
439 batch::{Action, DocChangeKind, ScopedStore, WriteTransactionResult},
440 collection::{
441 Collection, DittoDocument, DittoMutDocument, DocumentId, MutableValue,
442 PendingCursorOperation, PendingIdSpecificOperation,
443 },
444 collections::{CollectionsEvent, CollectionsEventHandler, PendingCollectionsOperation},
445 live_query::{
446 EventHandler, LiveQuery, LiveQueryEvent, LiveQueryMove, SingleDocumentEventHandler,
447 SingleDocumentLiveQueryEvent,
448 },
449 subscription::Subscription,
450 types::{DittoCounter, DittoMutableCounter, DittoMutableRegister, DittoRegister},
451 update::{UpdateOp, UpdateResult},
452};