Skip to main content

tricoredb/
document.rs

1//! Documents: collections, filters, updates, indexes and aggregation.
2
3use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6use serde_json::{json, Map, Value};
7
8use crate::client::Client;
9use crate::error::{Error, Result};
10use crate::response::Response;
11
12/// One JSON document as the server stores it. Every stored document carries an
13/// `_id`, whether the caller chose it or the server did.
14pub type Document = Map<String, Value>;
15
16/// A query predicate.
17///
18/// Field paths use dot notation (`"address.city"`) into nested objects. A
19/// document that lacks the path never matches — including for
20/// [`DocumentFilter::ne`].
21///
22/// This is not MongoDB's query language: there is no `or`, no `not` and no
23/// regular expression, because the server implements none of them.
24///
25/// ```
26/// use tricoredb::DocumentFilter;
27/// let cheap_widgets = DocumentFilter::and([
28///     DocumentFilter::eq("kind", "widget"),
29///     DocumentFilter::lt("price", 10),
30/// ]);
31/// # let _ = cheap_widgets;
32/// ```
33#[derive(Debug, Clone, PartialEq, Serialize)]
34#[serde(transparent)]
35pub struct DocumentFilter(Value);
36
37fn field_filter(variant: &str, field: &str, value: impl Into<Value>) -> DocumentFilter {
38    DocumentFilter(json!({variant: {"field": field, "value": value.into()}}))
39}
40
41impl DocumentFilter {
42    /// Match every document. Say this on purpose: there is no filter that means
43    /// it by accident.
44    pub fn all() -> Self {
45        DocumentFilter(Value::String("All".into()))
46    }
47
48    /// The field equals this value.
49    pub fn eq(field: &str, value: impl Into<Value>) -> Self {
50        field_filter("Eq", field, value)
51    }
52
53    /// The field exists and does not equal this value.
54    pub fn ne(field: &str, value: impl Into<Value>) -> Self {
55        field_filter("Ne", field, value)
56    }
57
58    /// The field is greater than this value.
59    pub fn gt(field: &str, value: impl Into<Value>) -> Self {
60        field_filter("Gt", field, value)
61    }
62
63    /// The field is greater than or equal to this value.
64    pub fn gte(field: &str, value: impl Into<Value>) -> Self {
65        field_filter("Gte", field, value)
66    }
67
68    /// The field is less than this value.
69    pub fn lt(field: &str, value: impl Into<Value>) -> Self {
70        field_filter("Lt", field, value)
71    }
72
73    /// The field is less than or equal to this value.
74    pub fn lte(field: &str, value: impl Into<Value>) -> Self {
75        field_filter("Lte", field, value)
76    }
77
78    /// A string field holds this substring, or an array field holds this
79    /// element.
80    pub fn contains(field: &str, value: impl Into<Value>) -> Self {
81        field_filter("Contains", field, value)
82    }
83
84    /// The field equals one of these values.
85    pub fn is_in<V: Into<Value>>(field: &str, values: impl IntoIterator<Item = V>) -> Self {
86        let values: Vec<Value> = values.into_iter().map(Into::into).collect();
87        DocumentFilter(json!({"In": {"field": field, "values": values}}))
88    }
89
90    /// Every one of these filters holds. With none given it matches
91    /// everything, which is what the server does with an empty `And`.
92    pub fn and(filters: impl IntoIterator<Item = DocumentFilter>) -> Self {
93        let filters: Vec<Value> = filters.into_iter().map(|f| f.0).collect();
94        DocumentFilter(json!({"And": filters}))
95    }
96}
97
98/// Field changes applied to one document: `set` overwrites the value at a dot
99/// path, `inc` adds a number to it. Every `set` happens before every `inc`.
100///
101/// `inc` never converts: incrementing a field that holds a string, a boolean,
102/// null, an array or an object is an error on the server. A missing field
103/// increments from zero, and a negative delta is how you subtract.
104#[derive(Debug, Clone, Default, PartialEq, Serialize)]
105pub struct DocumentUpdate {
106    /// Dot path to its new value.
107    #[serde(skip_serializing_if = "Map::is_empty")]
108    pub set: Map<String, Value>,
109    /// Dot path to a numeric delta.
110    #[serde(skip_serializing_if = "Map::is_empty")]
111    pub inc: Map<String, Value>,
112}
113
114impl DocumentUpdate {
115    /// An update that changes nothing yet.
116    pub fn new() -> Self {
117        Self::default()
118    }
119
120    /// Overwrite the value at a dot path.
121    pub fn set(mut self, path: &str, value: impl Into<Value>) -> Self {
122        self.set.insert(path.to_string(), value.into());
123        self
124    }
125
126    /// Add a number to the value at a dot path.
127    pub fn inc(mut self, path: &str, delta: impl Into<Value>) -> Self {
128        self.inc.insert(path.to_string(), delta.into());
129        self
130    }
131
132    fn is_empty(&self) -> bool {
133        self.set.is_empty() && self.inc.is_empty()
134    }
135}
136
137/// How [`AggregateStage::group`] derives a group key.
138#[derive(Debug, Clone, PartialEq, Serialize)]
139#[serde(transparent)]
140pub struct GroupKey(Value);
141
142impl GroupKey {
143    /// Group by the value at a dot path. A document that lacks the path groups
144    /// under null rather than being dropped.
145    pub fn field(path: &str) -> Self {
146        GroupKey(json!({"Field": path}))
147    }
148
149    /// Put the whole collection in one group under this constant — how a
150    /// collection-wide total is expressed.
151    pub fn constant(value: impl Into<Value>) -> Self {
152        GroupKey(json!({"Constant": value.into()}))
153    }
154}
155
156/// One reduction inside a group stage.
157#[derive(Debug, Clone, PartialEq, Serialize)]
158#[serde(transparent)]
159pub struct AccumulatorOp(Value);
160
161impl AccumulatorOp {
162    /// Total the numeric values at a field. Documents where it is missing or
163    /// not a number are ignored, so an absent field never contributes a zero.
164    pub fn sum(field: &str) -> Self {
165        AccumulatorOp(json!({"Sum": field}))
166    }
167
168    /// Average the numeric values at a field.
169    pub fn avg(field: &str) -> Self {
170        AccumulatorOp(json!({"Avg": field}))
171    }
172
173    /// The smallest value at a field.
174    pub fn min(field: &str) -> Self {
175        AccumulatorOp(json!({"Min": field}))
176    }
177
178    /// The largest value at a field.
179    pub fn max(field: &str) -> Self {
180        AccumulatorOp(json!({"Max": field}))
181    }
182
183    /// Count documents. It takes no field because it counts documents, not
184    /// values.
185    pub fn count() -> Self {
186        AccumulatorOp(Value::String("Count".into()))
187    }
188}
189
190/// An accumulator and the field it writes into the grouped document.
191///
192/// `output` must not be `_id` — that holds the group key — and must not repeat
193/// within one group stage.
194#[derive(Debug, Clone, PartialEq, Serialize)]
195pub struct GroupAccumulator {
196    /// The field the result is written to.
197    pub output: String,
198    /// The reduction to apply.
199    pub op: AccumulatorOp,
200}
201
202impl GroupAccumulator {
203    /// An accumulator writing into `output`.
204    pub fn new(output: &str, op: AccumulatorOp) -> Self {
205        Self {
206            output: output.to_string(),
207            op,
208        }
209    }
210}
211
212/// One sort key. After a group stage the addressable fields are `_id` and the
213/// accumulator outputs, not the original document's fields.
214#[derive(Debug, Clone, PartialEq, Serialize)]
215pub struct SortKey {
216    /// The field to sort on.
217    pub field: String,
218    /// Largest first when true.
219    pub descending: bool,
220}
221
222impl SortKey {
223    /// Smallest first.
224    pub fn ascending(field: &str) -> Self {
225        Self {
226            field: field.to_string(),
227            descending: false,
228        }
229    }
230
231    /// Largest first.
232    pub fn descending(field: &str) -> Self {
233        Self {
234            field: field.to_string(),
235            descending: true,
236        }
237    }
238}
239
240/// One stage of an aggregation pipeline.
241///
242/// Stages apply in the order given, and the order is meaning rather than
243/// style: a match before a group filters documents, after it filters groups.
244#[derive(Debug, Clone, PartialEq, Serialize)]
245#[serde(transparent)]
246pub struct AggregateStage(Value);
247
248impl AggregateStage {
249    /// Filter with the same matcher [`Client::document_find`] uses.
250    pub fn filter(filter: DocumentFilter) -> Self {
251        AggregateStage(json!({"Match": filter}))
252    }
253
254    /// Group by a key and apply accumulators.
255    pub fn group(by: GroupKey, accumulators: impl IntoIterator<Item = GroupAccumulator>) -> Self {
256        let accumulators: Vec<GroupAccumulator> = accumulators.into_iter().collect();
257        AggregateStage(json!({"Group": {"by": by, "accumulators": accumulators}}))
258    }
259
260    /// Order the documents.
261    pub fn sort(keys: impl IntoIterator<Item = SortKey>) -> Self {
262        let keys: Vec<SortKey> = keys.into_iter().collect();
263        AggregateStage(json!({"Sort": keys}))
264    }
265
266    /// Drop the first `n` documents.
267    pub fn skip(n: usize) -> Self {
268        AggregateStage(json!({"Skip": n}))
269    }
270
271    /// Keep at most `n` documents.
272    pub fn limit(n: usize) -> Self {
273        AggregateStage(json!({"Limit": n}))
274    }
275
276    /// Keep (`include` true) or drop (`include` false) these top-level fields.
277    /// Nested projection is refused by the server.
278    pub fn project(fields: impl IntoIterator<Item = impl Into<String>>, include: bool) -> Self {
279        let fields: Vec<String> = fields.into_iter().map(Into::into).collect();
280        AggregateStage(json!({"Project": {"fields": fields, "include": include}}))
281    }
282
283    /// Replace the documents with one that holds the input count under `field`.
284    pub fn count(field: &str) -> Self {
285        AggregateStage(json!({"Count": {"field": field}}))
286    }
287}
288
289/// One secondary index on a collection.
290#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
291pub struct DocumentIndex {
292    /// The index's name.
293    #[serde(rename = "index_name")]
294    pub name: String,
295    /// The field it indexes.
296    #[serde(default)]
297    pub field: String,
298    /// Whether it enforces uniqueness.
299    #[serde(default)]
300    pub unique: bool,
301}
302
303/// What [`Client::document_analyze`] measured.
304#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
305pub struct DocumentStats {
306    /// The collection that was analyzed.
307    #[serde(rename = "analyzed")]
308    pub collection: String,
309    /// How many documents it holds.
310    #[serde(default)]
311    pub document_count: i64,
312    /// How many fields carry a secondary index.
313    #[serde(default)]
314    pub indexed_fields: i64,
315}
316
317/// What [`Client::document_update_many`] changed.
318///
319/// `matched` counts the documents the filter selected; `modified` counts those
320/// whose contents actually changed, so rewriting a document to the value it
321/// already held is matched but not modified.
322#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
323pub struct UpdateCounts {
324    /// How many documents the filter selected.
325    #[serde(default)]
326    pub matched: i64,
327    /// How many of those actually changed.
328    #[serde(default)]
329    pub modified: i64,
330}
331
332/// Whether a by-id write hands back the document it touched, and which
333/// version of it.
334#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize)]
335pub enum ReturnDocument {
336    /// Return no document. The default.
337    #[default]
338    None,
339    /// The document as it was before the write. Absent when there was none —
340    /// an upsert that inserted.
341    Before,
342    /// The document as it is after the write.
343    After,
344}
345
346/// How [`Client::document_replace`] behaves. The default refuses a missing
347/// document, returns no document and checks no version.
348#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
349pub struct ReplaceOptions {
350    /// Create the document when it does not exist, rather than refuse.
351    pub upsert: bool,
352    /// Which version of the document to hand back.
353    pub return_document: ReturnDocument,
354    /// Apply only if the document's `_version` is still this. Needs a
355    /// versioned collection; `0` means "must not exist yet".
356    pub expected_version: Option<u64>,
357}
358
359/// What [`Client::document_replace`] did.
360#[derive(Debug, Clone, PartialEq, Deserialize)]
361pub struct ReplaceResult {
362    /// The document's id.
363    #[serde(default)]
364    pub id: String,
365    /// Whether a document now holds the new body.
366    #[serde(default)]
367    pub replaced: bool,
368    /// Whether the document was created by an upsert.
369    #[serde(default)]
370    pub inserted: bool,
371    /// The document asked for with [`ReplaceOptions::return_document`], when
372    /// there is one.
373    #[serde(default)]
374    pub document: Option<Document>,
375}
376
377/// What [`Client::document_purge_expired`] removed.
378#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
379pub struct PurgeCounts {
380    /// How many expired documents were deleted.
381    #[serde(default)]
382    pub purged: i64,
383    /// Whether expired documents remain past this call's batch.
384    #[serde(default)]
385    pub more: bool,
386}
387
388/// What [`Client::document_explain`] should run. The default explains an
389/// unbounded, unsorted find.
390#[derive(Debug, Clone, Default, PartialEq)]
391pub struct ExplainOptions {
392    /// Cap the documents returned.
393    pub limit: Option<usize>,
394    /// Order the documents.
395    pub sort: Vec<SortKey>,
396    /// Drop this many documents from the front.
397    pub skip: Option<usize>,
398}
399
400/// How a find ran, as [`Client::document_explain`] reports it.
401#[derive(Debug, Clone, PartialEq, Deserialize)]
402pub struct DocumentPlan {
403    /// The plan's name: `FindScan`, or an index lookup.
404    #[serde(default)]
405    pub plan: String,
406    /// The index that answered, when one did.
407    #[serde(default)]
408    pub index: Option<String>,
409    /// Whether an index answered.
410    #[serde(default)]
411    pub index_used: bool,
412    /// Why the collection was scanned, when it was.
413    #[serde(default)]
414    pub scan_reason: Option<String>,
415    /// Where a scan read from.
416    #[serde(default)]
417    pub scan_source: Option<String>,
418    /// The planner's estimate of the matching documents.
419    #[serde(default)]
420    pub estimated_rows: f64,
421    /// The planner's cost for the plan it chose.
422    #[serde(default)]
423    pub estimated_cost: f64,
424    /// How many documents were read.
425    #[serde(default)]
426    pub rows_examined: i64,
427    /// How many documents the find returned.
428    #[serde(default)]
429    pub rows_returned: i64,
430    /// How long it took, in microseconds.
431    #[serde(default)]
432    pub elapsed_us: i64,
433}
434
435/// What [`Client::document_rebuild_index`] rebuilt.
436#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
437pub struct IndexRebuild {
438    /// The index that was rebuilt.
439    #[serde(rename = "rebuilt")]
440    pub index_name: String,
441    /// How many documents were read.
442    #[serde(default)]
443    pub documents: i64,
444    /// How many index entries were written.
445    #[serde(default)]
446    pub entries: i64,
447}
448
449/// What [`Client::document_commit_transaction`] landed.
450#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
451pub struct DocumentCommit {
452    /// Whether the transaction committed.
453    #[serde(default)]
454    pub committed: bool,
455    /// How many writes landed with it.
456    #[serde(default)]
457    pub writes: i64,
458}
459
460/// A JSON type a [`FieldRule`] can demand. `Integer` is a number with no
461/// fractional part.
462#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
463#[serde(rename_all = "snake_case")]
464pub enum FieldType {
465    /// A string.
466    String,
467    /// Any number.
468    Number,
469    /// A number with no fractional part.
470    Integer,
471    /// `true` or `false`.
472    Boolean,
473    /// An object.
474    Object,
475    /// An array.
476    Array,
477    /// `null`.
478    Null,
479}
480
481/// What one field may hold. Every rule given must pass.
482#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
483pub struct FieldRule {
484    /// The JSON type the value must have.
485    #[serde(default, rename = "type", skip_serializing_if = "Option::is_none")]
486    pub field_type: Option<FieldType>,
487    /// The value must equal one of these.
488    #[serde(default, skip_serializing_if = "Vec::is_empty")]
489    pub one_of: Vec<Value>,
490    /// The smallest number allowed.
491    #[serde(default, skip_serializing_if = "Option::is_none")]
492    pub minimum: Option<f64>,
493    /// The largest number allowed.
494    #[serde(default, skip_serializing_if = "Option::is_none")]
495    pub maximum: Option<f64>,
496    /// The fewest characters of a string, or elements of an array.
497    #[serde(default, skip_serializing_if = "Option::is_none")]
498    pub min_length: Option<usize>,
499    /// The most characters of a string, or elements of an array.
500    #[serde(default, skip_serializing_if = "Option::is_none")]
501    pub max_length: Option<usize>,
502    /// A regular expression a string must contain a match for.
503    #[serde(default, skip_serializing_if = "Option::is_none")]
504    pub pattern: Option<String>,
505}
506
507impl FieldRule {
508    /// A rule demanding one JSON type.
509    pub fn of_type(field_type: FieldType) -> Self {
510        Self {
511            field_type: Some(field_type),
512            ..Self::default()
513        }
514    }
515}
516
517/// A document shape: required paths, per-path rules, and whether top-level
518/// fields the schema does not name are allowed.
519#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
520pub struct DocumentSchema {
521    /// Paths every document must have. A stored `null` counts as present.
522    #[serde(default)]
523    pub required: Vec<String>,
524    /// Path to rule, checked when the path is present.
525    #[serde(default)]
526    pub fields: BTreeMap<String, FieldRule>,
527    /// Whether a top-level field the schema does not name is allowed. `_id`
528    /// and `_version` always are.
529    #[serde(default = "yes")]
530    pub additional_fields: bool,
531}
532
533impl Default for DocumentSchema {
534    fn default() -> Self {
535        Self {
536            required: Vec::new(),
537            fields: BTreeMap::new(),
538            additional_fields: true,
539        }
540    }
541}
542
543fn yes() -> bool {
544    true
545}
546
547/// Rules a collection applies to every document written into it.
548#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
549pub struct CollectionOptions {
550    /// Every document written must satisfy this; a violation refuses the
551    /// write.
552    #[serde(default, skip_serializing_if = "Option::is_none")]
553    pub schema: Option<DocumentSchema>,
554    /// Path to the value a newly created document is given when it lacks the
555    /// path.
556    #[serde(default, skip_serializing_if = "Map::is_empty")]
557    pub defaults: Map<String, Value>,
558    /// Keep a `_version` counter on every document, which is what lets a write
559    /// name an expected version.
560    #[serde(default)]
561    pub versioned: bool,
562}
563
564/// One operation of [`Client::document_bulk_write`].
565///
566/// ```
567/// use serde_json::json;
568/// use tricoredb::{BulkOp, DocumentUpdate};
569/// let ops = [
570///     BulkOp::insert_with_id("a", json!({"n": 1})),
571///     BulkOp::update_one("a", DocumentUpdate::new().inc("n", 1)),
572///     BulkOp::delete("b"),
573/// ];
574/// # let _ = ops;
575/// ```
576#[derive(Debug, Clone, PartialEq, Serialize)]
577pub enum BulkOp {
578    /// Insert a document, under `id` when one is given.
579    Insert {
580        /// The id to insert under. `None` uses the document's `_id`, or
581        /// generates one.
582        id: Option<String>,
583        /// The document, which must be a JSON object.
584        document: Value,
585    },
586    /// Apply an update to one document by id.
587    UpdateOne {
588        /// The document's id.
589        id: String,
590        /// The changes to make.
591        update: DocumentUpdate,
592        /// Create the document from the update when it does not exist.
593        upsert: bool,
594        /// Apply only if the document's `_version` is still this.
595        expected_version: Option<u64>,
596    },
597    /// Swap one document's whole body.
598    Replace {
599        /// The document's id.
600        id: String,
601        /// The new body, which must be a JSON object.
602        document: Value,
603        /// Create the document when it does not exist.
604        upsert: bool,
605        /// Apply only if the document's `_version` is still this.
606        expected_version: Option<u64>,
607    },
608    /// Delete one document by id.
609    Delete {
610        /// The document's id.
611        id: String,
612        /// Apply only if the document's `_version` is still this.
613        expected_version: Option<u64>,
614    },
615}
616
617impl BulkOp {
618    /// Insert a document under its own `_id`, or a generated one.
619    pub fn insert(document: impl Into<Value>) -> Self {
620        BulkOp::Insert {
621            id: None,
622            document: document.into(),
623        }
624    }
625
626    /// Insert a document under an id you choose.
627    pub fn insert_with_id(id: &str, document: impl Into<Value>) -> Self {
628        BulkOp::Insert {
629            id: Some(id.to_string()),
630            document: document.into(),
631        }
632    }
633
634    /// Apply an update to one document by id.
635    pub fn update_one(id: &str, update: DocumentUpdate) -> Self {
636        BulkOp::UpdateOne {
637            id: id.to_string(),
638            update,
639            upsert: false,
640            expected_version: None,
641        }
642    }
643
644    /// Swap one document's whole body.
645    pub fn replace(id: &str, document: impl Into<Value>) -> Self {
646        BulkOp::Replace {
647            id: id.to_string(),
648            document: document.into(),
649            upsert: false,
650            expected_version: None,
651        }
652    }
653
654    /// Delete one document by id.
655    pub fn delete(id: &str) -> Self {
656        BulkOp::Delete {
657            id: id.to_string(),
658            expected_version: None,
659        }
660    }
661
662    /// Create the document when it does not exist. Changes nothing on an
663    /// insert or a delete, which have no such choice.
664    pub fn upsert(mut self) -> Self {
665        if let BulkOp::UpdateOne { upsert, .. } | BulkOp::Replace { upsert, .. } = &mut self {
666            *upsert = true;
667        }
668        self
669    }
670
671    /// Apply only if the document's `_version` is still `version`, compared
672    /// with the version it had before the batch. Changes nothing on an insert.
673    pub fn expected_version(mut self, version: u64) -> Self {
674        match &mut self {
675            BulkOp::UpdateOne {
676                expected_version, ..
677            }
678            | BulkOp::Replace {
679                expected_version, ..
680            }
681            | BulkOp::Delete {
682                expected_version, ..
683            } => *expected_version = Some(version),
684            BulkOp::Insert { .. } => {}
685        }
686        self
687    }
688}
689
690/// What [`Client::document_bulk_write`] applied.
691#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
692pub struct BulkWriteResult {
693    /// Documents inserted.
694    #[serde(default)]
695    pub inserted: i64,
696    /// Documents updated.
697    #[serde(default)]
698    pub updated: i64,
699    /// Documents created by an upsert.
700    #[serde(default)]
701    pub upserted: i64,
702    /// Documents replaced.
703    #[serde(default)]
704    pub replaced: i64,
705    /// Documents deleted.
706    #[serde(default)]
707    pub deleted: i64,
708    /// The ids of the inserted documents, in the order their operations were
709    /// given.
710    #[serde(default)]
711    pub inserted_ids: Vec<String>,
712}
713
714fn op(body: Value) -> Value {
715    json!({"Document": body})
716}
717
718impl Client {
719    /// Create a collection.
720    pub fn document_create_collection(&mut self, collection: &str) -> Result<()> {
721        self.send(op(json!({"CreateCollection": {"collection": collection}})))
722            .map(|_| ())
723    }
724
725    /// Drop a collection with its documents and indexes. Dropping one that does
726    /// not exist is an error.
727    pub fn document_drop_collection(&mut self, collection: &str) -> Result<()> {
728        self.send(op(json!({"DropCollection": {"collection": collection}})))
729            .map(|_| ())
730    }
731
732    /// Name every collection in the database.
733    pub fn document_list_collections(&mut self) -> Result<Vec<String>> {
734        #[derive(Deserialize)]
735        struct Collections {
736            #[serde(default)]
737            collections: Vec<String>,
738        }
739        let response = self.send(op(Value::String("ListCollections".into())))?;
740        let out: Collections = response.decode("ListCollections")?;
741        Ok(out.collections)
742    }
743
744    /// Insert a document under an id the server generates, and return that id.
745    pub fn document_insert(
746        &mut self,
747        collection: &str,
748        document: &impl Serialize,
749    ) -> Result<String> {
750        self.document_insert_inner(collection, Value::Null, document)
751    }
752
753    /// Insert a document under an id you choose. Inserting over an existing id
754    /// is an error rather than an overwrite — see [`Client::document_upsert_one`].
755    pub fn document_insert_with_id(
756        &mut self,
757        collection: &str,
758        id: &str,
759        document: &impl Serialize,
760    ) -> Result<String> {
761        self.document_insert_inner(collection, json!(id), document)
762    }
763
764    fn document_insert_inner(
765        &mut self,
766        collection: &str,
767        id: Value,
768        document: &impl Serialize,
769    ) -> Result<String> {
770        #[derive(Deserialize)]
771        struct Inserted {
772            #[serde(default)]
773            id: String,
774        }
775        let document = serde_json::to_value(document)
776            .map_err(|e| Error::invalid(format!("the document cannot be encoded as JSON: {e}")))?;
777        if !document.is_object() {
778            return Err(Error::invalid(
779                "a document must encode to a JSON object, not a scalar or an array",
780            ));
781        }
782        let response = self.send(op(json!({"Insert": {
783            "collection": collection,
784            "id": id,
785            "document": document,
786        }})))?;
787        let out: Inserted = response.decode("Insert")?;
788        if out.id.is_empty() {
789            return Err(Error::protocol("Insert answered with no document id"));
790        }
791        Ok(out.id)
792    }
793
794    /// Fetch one document by id. `None` when there is no such document, which
795    /// is how an absent document is told apart from a stored empty one.
796    pub fn document_get(&mut self, collection: &str, id: &str) -> Result<Option<Document>> {
797        let response = self.send(op(json!({"Get": {"collection": collection, "id": id}})))?;
798        Ok(documents_of(&response, "Get")?.into_iter().next())
799    }
800
801    /// Every document matching a filter.
802    pub fn document_find(
803        &mut self,
804        collection: &str,
805        filter: &DocumentFilter,
806    ) -> Result<Vec<Document>> {
807        self.document_find_inner(collection, filter, Value::Null)
808    }
809
810    /// At most `limit` documents matching a filter.
811    pub fn document_find_limit(
812        &mut self,
813        collection: &str,
814        filter: &DocumentFilter,
815        limit: usize,
816    ) -> Result<Vec<Document>> {
817        let limit = if limit == 0 {
818            Value::Null
819        } else {
820            json!(limit)
821        };
822        self.document_find_inner(collection, filter, limit)
823    }
824
825    fn document_find_inner(
826        &mut self,
827        collection: &str,
828        filter: &DocumentFilter,
829        limit: Value,
830    ) -> Result<Vec<Document>> {
831        let response = self.send(op(json!({"Find": {
832            "collection": collection,
833            "filter": filter,
834            "limit": limit,
835        }})))?;
836        documents_of(&response, "Find")
837    }
838
839    /// Set fields on an existing document. A missing id is an error: this is
840    /// not an upsert, and `_id` cannot be set.
841    pub fn document_update(
842        &mut self,
843        collection: &str,
844        id: &str,
845        set: &Map<String, Value>,
846    ) -> Result<()> {
847        self.send(op(json!({"Update": {
848            "collection": collection,
849            "id": id,
850            "set": set,
851        }})))
852        .map(|_| ())
853    }
854
855    /// Apply an update to one document by id. A missing id is an error; use
856    /// [`Client::document_upsert_one`] to create instead.
857    pub fn document_update_one(
858        &mut self,
859        collection: &str,
860        id: &str,
861        update: &DocumentUpdate,
862    ) -> Result<()> {
863        self.update_one(collection, id, update, false).map(|_| ())
864    }
865
866    /// Apply an update to one document by id, creating it from the update when
867    /// it does not exist. Returns whether it was created.
868    pub fn document_upsert_one(
869        &mut self,
870        collection: &str,
871        id: &str,
872        update: &DocumentUpdate,
873    ) -> Result<bool> {
874        let out = self.update_one(collection, id, update, true)?;
875        Ok(out.inserted)
876    }
877
878    fn update_one(
879        &mut self,
880        collection: &str,
881        id: &str,
882        update: &DocumentUpdate,
883        upsert: bool,
884    ) -> Result<UpdateOne> {
885        if update.is_empty() {
886            return Err(Error::invalid(
887                "an update must set or increment at least one field",
888            ));
889        }
890        let response = self.send(op(json!({"UpdateOne": {
891            "collection": collection,
892            "id": id,
893            "update": update,
894            "upsert": upsert,
895        }})))?;
896        response.decode("UpdateOne")
897    }
898
899    /// Apply an update to every document matching a filter. Never an upsert: a
900    /// filter that matches nothing changes nothing, and that is not an error.
901    pub fn document_update_many(
902        &mut self,
903        collection: &str,
904        filter: &DocumentFilter,
905        update: &DocumentUpdate,
906    ) -> Result<UpdateCounts> {
907        if update.is_empty() {
908            return Err(Error::invalid(
909                "an update must set or increment at least one field",
910            ));
911        }
912        let response = self.send(op(json!({"UpdateMany": {
913            "collection": collection,
914            "filter": filter,
915            "update": update,
916        }})))?;
917        response.decode("UpdateMany")
918    }
919
920    /// Delete one document by id. Deleting an absent document is not an error.
921    pub fn document_delete(&mut self, collection: &str, id: &str) -> Result<()> {
922        self.send(op(json!({"Delete": {"collection": collection, "id": id}})))
923            .map(|_| ())
924    }
925
926    /// Build a secondary index on a top-level field. A unique index over a
927    /// collection that already holds duplicates is refused before anything is
928    /// written.
929    pub fn document_create_index(
930        &mut self,
931        collection: &str,
932        index_name: &str,
933        field: &str,
934        unique: bool,
935    ) -> Result<()> {
936        self.send(op(json!({"CreateIndex": {
937            "collection": collection,
938            "index_name": index_name,
939            "field": field,
940            "unique": unique,
941        }})))
942        .map(|_| ())
943    }
944
945    /// Remove a named index.
946    pub fn document_drop_index(&mut self, collection: &str, index_name: &str) -> Result<()> {
947        self.send(op(json!({"DropIndex": {
948            "collection": collection,
949            "index_name": index_name,
950        }})))
951        .map(|_| ())
952    }
953
954    /// List a collection's indexes.
955    pub fn document_list_indexes(&mut self, collection: &str) -> Result<Vec<DocumentIndex>> {
956        #[derive(Deserialize)]
957        struct Indexes {
958            #[serde(default)]
959            indexes: Vec<DocumentIndex>,
960        }
961        let response = self.send(op(json!({"ListIndexes": {"collection": collection}})))?;
962        let out: Indexes = response.decode("ListIndexes")?;
963        Ok(out.indexes)
964    }
965
966    /// Collect the statistics the planner uses to choose between an index
967    /// lookup and a scan.
968    pub fn document_analyze(&mut self, collection: &str) -> Result<DocumentStats> {
969        let response = self.send(op(json!({"Analyze": {"collection": collection}})))?;
970        response.decode("Analyze")
971    }
972
973    /// Run an aggregation pipeline. An empty pipeline returns the collection
974    /// unchanged.
975    ///
976    /// ```no_run
977    /// # fn main() -> tricoredb::Result<()> {
978    /// # let mut db = tricoredb::Client::connect(&tricoredb::Options::default())?;
979    /// use tricoredb::{AccumulatorOp, AggregateStage, DocumentFilter, GroupAccumulator, GroupKey};
980    /// let totals = db.document_aggregate("orders", &[
981    ///     AggregateStage::filter(DocumentFilter::eq("status", "paid")),
982    ///     AggregateStage::group(
983    ///         GroupKey::field("customer"),
984    ///         [GroupAccumulator::new("total", AccumulatorOp::sum("amount"))],
985    ///     ),
986    /// ])?;
987    /// # let _ = totals; Ok(()) }
988    /// ```
989    pub fn document_aggregate(
990        &mut self,
991        collection: &str,
992        pipeline: &[AggregateStage],
993    ) -> Result<Vec<Document>> {
994        let response = self.send(op(json!({"Aggregate": {
995            "collection": collection,
996            "pipeline": pipeline,
997        }})))?;
998        documents_of(&response, "Aggregate")
999    }
1000
1001    /// Swap a document's whole body for a new one.
1002    ///
1003    /// [`Client::document_update`] merges, so a field left out survives; after
1004    /// a replace the stored body is exactly `document` plus `_id`. An `_id`
1005    /// inside `document` may agree with `id` or be absent, never disagree.
1006    pub fn document_replace(
1007        &mut self,
1008        collection: &str,
1009        id: &str,
1010        document: &impl Serialize,
1011        options: &ReplaceOptions,
1012    ) -> Result<ReplaceResult> {
1013        let mut body = Map::new();
1014        body.insert("collection".into(), json!(collection));
1015        body.insert("id".into(), json!(id));
1016        body.insert("document".into(), object_value(document)?);
1017        body.insert("upsert".into(), json!(options.upsert));
1018        if options.return_document != ReturnDocument::None {
1019            body.insert("return".into(), json!(options.return_document));
1020        }
1021        if let Some(version) = options.expected_version {
1022            body.insert("expected_version".into(), json!(version));
1023        }
1024        let response = self.send(op(json!({"Replace": Value::Object(body)})))?;
1025        response.decode("Replace")
1026    }
1027
1028    /// Insert several documents as one atomic batch — every document lands or
1029    /// none does — and return their ids in order.
1030    ///
1031    /// Each document's id is its own `_id` when it carries one, and is
1032    /// generated otherwise.
1033    pub fn document_insert_many(
1034        &mut self,
1035        collection: &str,
1036        documents: &[impl Serialize],
1037    ) -> Result<Vec<String>> {
1038        #[derive(Deserialize)]
1039        struct Inserted {
1040            #[serde(default)]
1041            ids: Vec<String>,
1042        }
1043        let documents = documents
1044            .iter()
1045            .map(object_value)
1046            .collect::<Result<Vec<Value>>>()?;
1047        let response = self.send(op(json!({"InsertMany": {
1048            "collection": collection,
1049            "documents": documents,
1050        }})))?;
1051        let out: Inserted = response.decode("InsertMany")?;
1052        Ok(out.ids)
1053    }
1054
1055    /// Delete every document matching a filter, returning how many went.
1056    pub fn document_delete_many(
1057        &mut self,
1058        collection: &str,
1059        filter: &DocumentFilter,
1060    ) -> Result<i64> {
1061        #[derive(Deserialize)]
1062        struct Deleted {
1063            #[serde(default)]
1064            deleted: i64,
1065        }
1066        let response = self.send(op(json!({"DeleteMany": {
1067            "collection": collection,
1068            "filter": filter,
1069        }})))?;
1070        let out: Deleted = response.decode("DeleteMany")?;
1071        Ok(out.deleted)
1072    }
1073
1074    /// Move a collection, with its documents and indexes, to a new name.
1075    /// Refused when `to` already exists.
1076    pub fn document_rename_collection(&mut self, collection: &str, to: &str) -> Result<()> {
1077        self.send(op(json!({"RenameCollection": {
1078            "collection": collection,
1079            "to": to,
1080        }})))
1081        .map(|_| ())
1082    }
1083
1084    /// Delete the documents a TTL index says have expired, now.
1085    pub fn document_purge_expired(&mut self, collection: &str) -> Result<PurgeCounts> {
1086        let response = self.send(op(json!({"PurgeExpired": {"collection": collection}})))?;
1087        response.decode("PurgeExpired")
1088    }
1089
1090    /// Run a find and describe how it ran instead of returning its documents.
1091    pub fn document_explain(
1092        &mut self,
1093        collection: &str,
1094        filter: &DocumentFilter,
1095        options: &ExplainOptions,
1096    ) -> Result<DocumentPlan> {
1097        let mut body = Map::new();
1098        body.insert("collection".into(), json!(collection));
1099        body.insert("filter".into(), json!(filter));
1100        if let Some(limit) = options.limit {
1101            body.insert("limit".into(), json!(limit));
1102        }
1103        if !options.sort.is_empty() {
1104            body.insert("sort".into(), json!(options.sort));
1105        }
1106        if let Some(skip) = options.skip {
1107            body.insert("skip".into(), json!(skip));
1108        }
1109        let response = self.send(op(json!({"Explain": Value::Object(body)})))?;
1110        response.decode("Explain")
1111    }
1112
1113    /// Open a transaction spanning requests, and return its id.
1114    ///
1115    /// Nothing joins the transaction by itself: pass the id to
1116    /// [`Client::set_request_txn`], and every later request reads and writes
1117    /// inside it, seeing its own writes and nobody else's since it began. With
1118    /// `serializable`, its reads are validated at commit as well.
1119    ///
1120    /// ```no_run
1121    /// # fn main() -> tricoredb::Result<()> {
1122    /// # let mut db = tricoredb::Client::connect(&tricoredb::Options::default())?;
1123    /// use serde_json::json;
1124    /// let txn = db.document_begin_transaction(false)?;
1125    /// db.set_request_txn(Some(&txn));
1126    /// db.document_insert_with_id("orders", "o1", &json!({"total": 10}))?;
1127    /// db.document_commit_transaction(&txn)?;
1128    /// # Ok(()) }
1129    /// ```
1130    pub fn document_begin_transaction(&mut self, serializable: bool) -> Result<String> {
1131        #[derive(Deserialize)]
1132        struct Begun {
1133            #[serde(default)]
1134            txn: String,
1135        }
1136        let response = self.send_in(
1137            op(json!({"BeginTransaction": {"serializable": serializable}})),
1138            None,
1139        )?;
1140        let out: Begun = response.decode("BeginTransaction")?;
1141        if out.txn.is_empty() {
1142            return Err(Error::protocol(
1143                "BeginTransaction answered with no transaction id",
1144            ));
1145        }
1146        Ok(out.txn)
1147    }
1148
1149    /// Commit a transaction: every write lands as one atomic batch, or a
1150    /// conflict is returned and nothing lands.
1151    ///
1152    /// Clears [`Client::set_request_txn`] when it names this transaction,
1153    /// whatever the outcome: a transaction that failed to commit is over.
1154    pub fn document_commit_transaction(&mut self, txn: &str) -> Result<DocumentCommit> {
1155        let outcome = self.send_in(op(json!("CommitTransaction")), Some(txn));
1156        self.forget_txn(txn);
1157        outcome?.decode("CommitTransaction")
1158    }
1159
1160    /// Discard a transaction and everything written inside it.
1161    pub fn document_rollback_transaction(&mut self, txn: &str) -> Result<()> {
1162        let outcome = self.send_in(op(json!("RollbackTransaction")), Some(txn));
1163        self.forget_txn(txn);
1164        outcome.map(|_| ())
1165    }
1166
1167    /// Rebuild one index from the stored documents, replacing its entries in
1168    /// one batch.
1169    pub fn document_rebuild_index(
1170        &mut self,
1171        collection: &str,
1172        index_name: &str,
1173    ) -> Result<IndexRebuild> {
1174        let response = self.send(op(json!({"RebuildIndex": {
1175            "collection": collection,
1176            "index_name": index_name,
1177        }})))?;
1178        response.decode("RebuildIndex")
1179    }
1180
1181    /// Set a collection's options, replacing any it had, and return how many
1182    /// stored documents were checked against the new schema. Refused when an
1183    /// existing document would break it.
1184    pub fn document_set_collection_options(
1185        &mut self,
1186        collection: &str,
1187        options: &CollectionOptions,
1188    ) -> Result<i64> {
1189        #[derive(Deserialize)]
1190        struct Updated {
1191            #[serde(default)]
1192            documents_checked: i64,
1193        }
1194        let response = self.send(op(json!({"SetCollectionOptions": {
1195            "collection": collection,
1196            "options": options,
1197        }})))?;
1198        let out: Updated = response.decode("SetCollectionOptions")?;
1199        Ok(out.documents_checked)
1200    }
1201
1202    /// Read a collection's options.
1203    pub fn document_get_collection_options(
1204        &mut self,
1205        collection: &str,
1206    ) -> Result<CollectionOptions> {
1207        #[derive(Deserialize)]
1208        struct Wire {
1209            #[serde(default)]
1210            options: CollectionOptions,
1211        }
1212        let response = self.send(op(
1213            json!({"GetCollectionOptions": {"collection": collection}}),
1214        ))?;
1215        let out: Wire = response.decode("GetCollectionOptions")?;
1216        Ok(out.options)
1217    }
1218
1219    /// Many inserts, updates, replaces and deletes on one collection, applied
1220    /// in order as one atomic batch: every operation lands or none does. A
1221    /// later operation sees the effect of an earlier one on the same id.
1222    pub fn document_bulk_write(
1223        &mut self,
1224        collection: &str,
1225        ops: &[BulkOp],
1226    ) -> Result<BulkWriteResult> {
1227        let response = self.send(op(json!({"BulkWrite": {
1228            "collection": collection,
1229            "ops": ops,
1230        }})))?;
1231        response.decode("BulkWrite")
1232    }
1233}
1234
1235/// A document as the JSON object it must be.
1236fn object_value(document: &impl Serialize) -> Result<Value> {
1237    let document = serde_json::to_value(document)
1238        .map_err(|e| Error::invalid(format!("the document cannot be encoded as JSON: {e}")))?;
1239    if !document.is_object() {
1240        return Err(Error::invalid(
1241            "a document must encode to a JSON object, not a scalar or an array",
1242        ));
1243    }
1244    Ok(document)
1245}
1246
1247#[derive(Deserialize)]
1248struct UpdateOne {
1249    #[serde(default)]
1250    #[allow(dead_code)]
1251    updated: bool,
1252    #[serde(default)]
1253    inserted: bool,
1254}
1255
1256fn documents_of(response: &Response, what: &str) -> Result<Vec<Document>> {
1257    let data = response.expect("Documents", what)?;
1258    serde_json::from_value(data.clone())
1259        .map_err(|e| Error::protocol(format!("malformed Documents payload from {what}: {e}")))
1260}
1261
1262#[cfg(test)]
1263mod tests {
1264    use super::*;
1265
1266    fn wire(value: &impl Serialize) -> Value {
1267        serde_json::to_value(value).unwrap()
1268    }
1269
1270    #[test]
1271    fn filters_are_externally_tagged() {
1272        assert_eq!(wire(&DocumentFilter::all()), json!("All"));
1273        assert_eq!(
1274            wire(&DocumentFilter::eq("kind", "widget")),
1275            json!({"Eq": {"field": "kind", "value": "widget"}})
1276        );
1277        assert_eq!(
1278            wire(&DocumentFilter::lt("price", 10)),
1279            json!({"Lt": {"field": "price", "value": 10}})
1280        );
1281        assert_eq!(
1282            wire(&DocumentFilter::is_in("id", ["a", "b"])),
1283            json!({"In": {"field": "id", "values": ["a", "b"]}})
1284        );
1285    }
1286
1287    #[test]
1288    fn and_nests_its_children_in_order() {
1289        let filter = DocumentFilter::and([DocumentFilter::eq("a", 1), DocumentFilter::gt("b", 2)]);
1290        let value = wire(&filter);
1291        assert_eq!(value["And"][0]["Eq"]["field"], "a");
1292        assert_eq!(value["And"][1]["Gt"]["field"], "b");
1293    }
1294
1295    #[test]
1296    fn an_update_omits_the_half_it_does_not_use() {
1297        let update = DocumentUpdate::new().set("name", "ada");
1298        assert_eq!(wire(&update), json!({"set": {"name": "ada"}}));
1299        let update = DocumentUpdate::new().inc("hits", 1);
1300        assert_eq!(wire(&update), json!({"inc": {"hits": 1}}));
1301        assert!(DocumentUpdate::new().is_empty());
1302    }
1303
1304    #[test]
1305    fn pipeline_stages_keep_the_servers_own_names() {
1306        assert_eq!(
1307            wire(&AggregateStage::filter(DocumentFilter::all())),
1308            json!({"Match": "All"})
1309        );
1310        assert_eq!(wire(&AggregateStage::skip(2)), json!({"Skip": 2}));
1311        assert_eq!(wire(&AggregateStage::limit(5)), json!({"Limit": 5}));
1312        assert_eq!(
1313            wire(&AggregateStage::count("n")),
1314            json!({"Count": {"field": "n"}})
1315        );
1316        assert_eq!(
1317            wire(&AggregateStage::project(["a", "b"], true)),
1318            json!({"Project": {"fields": ["a", "b"], "include": true}})
1319        );
1320    }
1321
1322    #[test]
1323    fn a_group_stage_carries_its_key_and_accumulators() {
1324        let stage = AggregateStage::group(
1325            GroupKey::field("customer"),
1326            [
1327                GroupAccumulator::new("total", AccumulatorOp::sum("amount")),
1328                GroupAccumulator::new("orders", AccumulatorOp::count()),
1329            ],
1330        );
1331        let value = wire(&stage);
1332        assert_eq!(value["Group"]["by"], json!({"Field": "customer"}));
1333        assert_eq!(value["Group"]["accumulators"][0]["output"], "total");
1334        assert_eq!(
1335            value["Group"]["accumulators"][0]["op"],
1336            json!({"Sum": "amount"})
1337        );
1338        assert_eq!(value["Group"]["accumulators"][1]["op"], json!("Count"));
1339    }
1340
1341    #[test]
1342    fn sort_keys_say_which_way_they_go() {
1343        assert_eq!(
1344            wire(&AggregateStage::sort([SortKey::descending("total")])),
1345            json!({"Sort": [{"field": "total", "descending": true}]})
1346        );
1347        assert_eq!(
1348            wire(&SortKey::ascending("name")),
1349            json!({"field": "name", "descending": false})
1350        );
1351    }
1352}