aragog/db/
database_record.rs

1use arangors_lite::{AqlQuery, Document};
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::fmt::{self, Display, Formatter};
5
6use crate::db::database_service;
7use crate::db::database_service::{query_records, query_records_in_batches, raw_query_records};
8use crate::query::{Query, QueryCursor, QueryResult};
9use crate::{DatabaseAccess, EdgeRecord, Error, OperationOptions, Record};
10use std::ops::{Deref, DerefMut};
11
12/// Struct representing database stored documents.
13///
14/// The document of type `T` mut implement [`Record`]
15///
16/// # Note
17///
18/// `DatabaseRecord` implements `Deref` and `DerefMut` into `T`
19///
20/// [`Record`]: crate::Record
21#[derive(Serialize, Deserialize, Debug, Clone)]
22pub struct DatabaseRecord<T> {
23    /// The Document unique and indexed `_key`
24    #[serde(rename = "_key")]
25    pub(crate) key: String,
26    /// The Document unique and indexed `_id`
27    #[serde(rename = "_id")]
28    pub(crate) id: String,
29    /// The Document revision `_rev`
30    #[serde(rename = "_rev")]
31    pub(crate) rev: String,
32    /// The deserialized stored document
33    #[serde(flatten)]
34    pub record: T,
35}
36
37#[allow(dead_code)]
38impl<T: Record> DatabaseRecord<T> {
39    #[maybe_async::maybe_async]
40    async fn __create_with_options<D>(
41        mut record: T,
42        key: Option<String>,
43        db_accessor: &D,
44        options: OperationOptions,
45    ) -> Result<Self, Error>
46    where
47        D: DatabaseAccess + ?Sized,
48    {
49        let launch_hooks = !options.ignore_hooks;
50        if launch_hooks {
51            record.before_create_hook(db_accessor).await?;
52        }
53        let mut res =
54            database_service::create_record(record, key, db_accessor, T::COLLECTION_NAME, options)
55                .await?;
56        if launch_hooks {
57            res.record.after_create_hook(db_accessor).await?;
58        }
59        Ok(res)
60    }
61
62    /// Creates a document in database.
63    /// The function will write a new document and return a database record containing the newly created key
64    ///
65    /// # Note
66    ///
67    /// This method should be used for very specific cases, prefer using `create` instead.
68    /// If you want global operation options (always wait for sync, always ignore hooks, etc)
69    /// configure your [`DatabaseConnection`] with `with_operation_options` to have a customs set
70    /// of default options.
71    ///
72    /// # Hooks
73    ///
74    /// This function will launch `T` hooks `before_create` and `after_create` unless the `options`
75    /// argument disables hooks.
76    ///
77    /// # Arguments
78    ///
79    /// * `record` - The document to create, it will be returned exactly as the `DatabaseRecord<T>` record
80    /// * `db_accessor` - database connection reference
81    /// * `options` - Operation options to apply
82    ///
83    /// # Returns
84    ///
85    /// On success a new instance of `Self` is returned, with the `key` value filled and `record` filled with the
86    /// argument value.
87    /// An [`Error`] is returned if the operation or the hooks failed.
88    ///
89    /// [`Error`]: crate::Error
90    /// [`DatabaseConnection`]: crate::DatabaseConnection
91    #[maybe_async::maybe_async]
92    pub async fn create_with_options<D>(
93        record: T,
94        db_accessor: &D,
95        options: OperationOptions,
96    ) -> Result<Self, Error>
97    where
98        D: DatabaseAccess + ?Sized,
99    {
100        Self::__create_with_options(record, None, db_accessor, options).await
101    }
102
103    /// Creates a document in database with a custom key.
104    /// The function will write a new document and return a database record containing the newly created key
105    ///
106    /// # Note
107    ///
108    /// This method should be used for very specific cases, prefer using `create_with_key` instead.
109    /// If you want global operation options (always wait for sync, always ignore hooks, etc)
110    /// configure your [`DatabaseConnection`] with `with_operation_options` to have a customs set
111    /// of default options.
112    ///
113    /// # Hooks
114    ///
115    /// This function will launch `T` hooks `before_create` and `after_create` unless the `options`
116    /// argument disables hooks.
117    ///
118    /// # Arguments
119    ///
120    /// * `record` - The document to create, it will be returned exactly as the `DatabaseRecord<T>` record
121    /// * `key` - The custom key to apply
122    /// * `db_accessor` - database connection reference
123    /// * `options` - Operation options to apply
124    ///
125    /// # Returns
126    ///
127    /// On success a new instance of `Self` is returned, with the `key` value filled and `record` filled with the
128    /// argument value.
129    /// An [`Error`] is returned if the operation or the hooks failed.
130    ///
131    /// [`Error`]: crate::Error
132    /// [`DatabaseConnection`]: crate::DatabaseConnection
133    #[maybe_async::maybe_async]
134    pub async fn create_with_key_and_options<D>(
135        record: T,
136        key: String,
137        db_accessor: &D,
138        options: OperationOptions,
139    ) -> Result<Self, Error>
140    where
141        D: DatabaseAccess + ?Sized,
142    {
143        Self::__create_with_options(record, Some(key), db_accessor, options).await
144    }
145
146    /// Creates a document in database.
147    /// The function will write a new document and return a database record containing the newly created key
148    ///
149    /// # Hooks
150    ///
151    /// This function will launch `T` hooks `before_create` and `after_create` unless the `db_accessor`
152    /// operations options specifically disable hooks.
153    ///
154    /// # Arguments
155    ///
156    /// * `record` - The document to create, it will be returned exactly as the `DatabaseRecord<T>` record
157    /// * `db_accessor` - database connection reference
158    ///
159    /// # Returns
160    ///
161    /// On success a new instance of `Self` is returned, with the `key` value filled and `record` filled with the
162    /// argument value.
163    /// An [`Error`] is returned if the operation or the hooks failed.
164    ///
165    /// [`Error`]: crate::Error
166    #[maybe_async::maybe_async]
167    pub async fn create<D>(record: T, db_accessor: &D) -> Result<Self, Error>
168    where
169        D: DatabaseAccess + ?Sized,
170    {
171        Self::create_with_options(record, db_accessor, db_accessor.operation_options()).await
172    }
173
174    /// Creates a document in database with a custom key.
175    /// The function will write a new document and return a database record containing the newly created key
176    ///
177    /// # Hooks
178    ///
179    /// This function will launch `T` hooks `before_create` and `after_create` unless the `db_accessor`
180    /// operations options specifically disable hooks.
181    ///
182    /// # Arguments
183    ///
184    /// * `record` - The document to create, it will be returned exactly as the `DatabaseRecord<T>` record
185    /// * `key` - the custom document key
186    /// * `db_accessor` - database connection reference
187    ///
188    /// # Returns
189    ///
190    /// On success a new instance of `Self` is returned, with the `key` value filled and `record` filled with the
191    /// argument value.
192    /// An [`Error`] is returned if the operation or the hooks failed.
193    ///
194    /// [`Error`]: crate::Error
195    #[maybe_async::maybe_async]
196    pub async fn create_with_key<D>(record: T, key: String, db_accessor: &D) -> Result<Self, Error>
197    where
198        D: DatabaseAccess + ?Sized,
199    {
200        Self::create_with_key_and_options(record, key, db_accessor, db_accessor.operation_options())
201            .await
202    }
203
204    /// Creates a document in database.
205    /// The function will write a new document and return a database record containing the newly created key.
206    ///
207    /// # Note
208    ///
209    /// This function will **override** the default operations options:
210    /// - Revision will be ignored
211    /// - Hooks will be skipped
212    /// and should be used sparingly.
213    ///
214    /// # Hooks
215    ///
216    /// This function will skip all hooks.
217    ///
218    /// # Arguments
219    ///
220    /// * `record` - The document to create, it will be returned exactly as the `DatabaseRecord<T>` record
221    /// * `db_accessor` - database connection reference
222    ///
223    /// # Returns
224    ///
225    /// On success a new instance of `Self` is returned, with the `key` value filled and `record` filled with the
226    /// argument value
227    /// On failure an [`Error`] is returned.
228    ///
229    /// [`Error`]: crate::Error
230    #[maybe_async::maybe_async]
231    pub async fn force_create<D>(record: T, db_accessor: &D) -> Result<Self, Error>
232    where
233        D: DatabaseAccess + ?Sized,
234    {
235        Self::create_with_options(
236            record,
237            db_accessor,
238            db_accessor
239                .operation_options()
240                .ignore_revs(true)
241                .ignore_hooks(true),
242        )
243        .await
244    }
245
246    /// Writes in the database the new state of the record, "saving it".
247    ///
248    /// # Note
249    ///
250    /// This method should be used for very specific cases, prefer using `save` instead.
251    /// If you want global operation options (always wait for sync, always ignore hooks, etc)
252    /// configure your [`DatabaseConnection`] with `with_operation_options` to have a customs set
253    /// of default options.
254    ///
255    /// # Hooks
256    ///
257    /// This function will launch `T` hooks `before_save` and `after_save` unless the `options`
258    /// argument disables hooks.
259    ///
260    /// # Arguments:
261    ///
262    /// * `db_accessor` - database connection reference
263    /// * `options` - Operation options to apply
264    ///
265    /// # Returns
266    ///
267    /// On success `()` is returned, meaning that the current instance is up to date with the database state.
268    /// An [`Error`] is returned if the operation or the hooks failed.
269    ///
270    /// [`Error`]: crate::Error
271    /// [`DatabaseConnection`]: crate::DatabaseConnection
272    #[maybe_async::maybe_async]
273    pub async fn save_with_options<D>(
274        &mut self,
275        db_accessor: &D,
276        options: OperationOptions,
277    ) -> Result<(), Error>
278    where
279        D: DatabaseAccess + ?Sized,
280    {
281        let launch_hooks = !options.ignore_hooks;
282        if launch_hooks {
283            self.record.before_save_hook(db_accessor).await?;
284        }
285        let mut new_record = database_service::update_record(
286            self.clone(),
287            self.key(),
288            db_accessor,
289            T::COLLECTION_NAME,
290            options,
291        )
292        .await?;
293        if launch_hooks {
294            new_record.record.after_save_hook(db_accessor).await?;
295        }
296        *self = new_record;
297        Ok(())
298    }
299
300    /// Writes in the database the new state of the record, "saving it".
301    ///
302    /// # Hooks
303    ///
304    /// This function will launch `T` hooks `before_save` and `after_save` unless the `db_accessor`
305    /// operations options specifically disable hooks.
306    ///
307    /// # Arguments:
308    ///
309    /// * `db_accessor` - database connection reference
310    ///
311    /// # Returns
312    ///
313    /// On success `()` is returned, meaning that the current instance is up to date with the database state.
314    /// An [`Error`] is returned if the operation or the hooks failed.
315    ///
316    /// [`Error`]: crate::Error
317    #[maybe_async::maybe_async]
318    pub async fn save<D>(&mut self, db_accessor: &D) -> Result<(), Error>
319    where
320        D: DatabaseAccess + ?Sized,
321    {
322        self.save_with_options(db_accessor, db_accessor.operation_options())
323            .await
324    }
325
326    /// Writes in the database the new state of the record.
327    ///
328    /// # Note
329    ///
330    /// This function will **override** the default operations options:
331    /// - Revision will be ignored
332    /// - Hooks will be skipped
333    /// and should be used sparingly.
334    ///
335    /// # Hooks
336    ///
337    /// This function will skip all hooks.
338    ///
339    /// # Arguments:
340    ///
341    /// * `db_accessor` - database connection reference
342    ///
343    /// # Returns
344    ///
345    /// On success `()` is returned, meaning that the current instance is up to date with the database state.
346    /// On failure an [`Error`] is returned.
347    ///
348    /// [`Error`]: crate::Error
349    #[maybe_async::maybe_async]
350    pub async fn force_save<D>(&mut self, db_accessor: &D) -> Result<(), Error>
351    where
352        D: DatabaseAccess + ?Sized,
353    {
354        self.save_with_options(
355            db_accessor,
356            db_accessor
357                .operation_options()
358                .ignore_hooks(true)
359                .ignore_revs(true),
360        )
361        .await
362    }
363
364    /// Removes the record from the database.
365    /// The structure won't be freed or emptied but the document won't exist in the global state
366    ///
367    /// # Note
368    ///
369    /// This method should be used for very specific cases, prefer using `delete` instead.
370    /// If you want global operation options (always wait for sync, always ignore hooks, etc)
371    /// configure your [`DatabaseConnection`] with `with_operation_options` to have a customs set
372    /// of default options
373    ///
374    /// # Hooks
375    ///
376    /// This function will launch `T` hooks  `before_delete` and `after_delete` unless the `options`
377    /// argument disables hooks.
378    ///
379    /// # Arguments:
380    ///
381    /// * `db_accessor` - database connection reference
382    ///
383    /// # Returns
384    ///
385    /// On success `()` is returned, meaning that the record is now deleted, the structure should not be used afterwards.
386    /// An [`Error`] is returned if the operation or the hooks failed.
387    ///
388    /// [`Error`]: crate::Error
389    /// [`DatabaseConnection`]: crate::DatabaseConnection
390    #[maybe_async::maybe_async]
391    pub async fn delete_with_options<D>(
392        &mut self,
393        db_accessor: &D,
394        options: OperationOptions,
395    ) -> Result<(), Error>
396    where
397        D: DatabaseAccess + ?Sized,
398    {
399        let launch_hooks = !options.ignore_hooks;
400        if launch_hooks {
401            self.record.before_delete_hook(db_accessor).await?;
402        }
403        database_service::remove_record::<T, D>(
404            self.key(),
405            db_accessor,
406            T::COLLECTION_NAME,
407            options,
408        )
409        .await?;
410        if launch_hooks {
411            self.record.after_delete_hook(db_accessor).await?;
412        }
413        Ok(())
414    }
415
416    /// Removes the record from the database.
417    /// The structure won't be freed or emptied but the document won't exist in the global state
418    ///
419    /// # Hooks
420    ///
421    /// This function will launch `T` hooks  `before_delete` and `after_delete` unless the `db_accessor`
422    /// operations options specifically disable hooks.
423    ///
424    /// # Arguments:
425    ///
426    /// * `db_accessor` - database connection reference
427    ///
428    /// # Returns
429    ///
430    /// On success `()` is returned, meaning that the record is now deleted, the structure should not be used afterwards.
431    /// An [`Error`] is returned if the operation or the hooks failed.
432    ///
433    /// [`Error`]: crate::Error
434    #[maybe_async::maybe_async]
435    pub async fn delete<D>(&mut self, db_accessor: &D) -> Result<(), Error>
436    where
437        D: DatabaseAccess + ?Sized,
438    {
439        self.delete_with_options(db_accessor, db_accessor.operation_options())
440            .await
441    }
442
443    /// Removes the record from the database.
444    /// The structure won't be freed or emptied but the document won't exist in the global state
445    ///
446    /// # Note
447    ///
448    /// This function will **override** the default operations options:
449    /// - Revision will be ignored
450    /// - Hooks will be skipped
451    /// and should be used sparingly.
452    ///
453    /// # Hooks
454    ///
455    /// This function will skip all hooks.
456    ///
457    /// # Arguments:
458    ///
459    /// * `db_accessor` - database connection reference
460    ///
461    /// # Returns
462    ///
463    /// On success `()` is returned, meaning that the record is now deleted, the structure should not be used afterwards.
464    /// On failure an [`Error`] is returned.
465    ///
466    /// [`Error`]: crate::Error
467    #[maybe_async::maybe_async]
468    pub async fn force_delete<D>(&mut self, db_accessor: &D) -> Result<(), Error>
469    where
470        D: DatabaseAccess + ?Sized,
471    {
472        self.delete_with_options(
473            db_accessor,
474            db_accessor
475                .operation_options()
476                .ignore_revs(true)
477                .ignore_hooks(true),
478        )
479        .await
480    }
481
482    /// Creates and returns edge between `from_record` and `target_record`.
483    ///
484    /// # Hooks
485    ///
486    /// This function will launch `T` hooks `before_create` and `after_create`.
487    ///
488    /// # Example
489    /// ```rust
490    /// # use aragog::{DatabaseRecord, EdgeRecord, Record, DatabaseConnection};
491    /// # use serde::{Serialize, Deserialize};
492    /// #
493    /// # #[derive(Record, Clone, Serialize, Deserialize)]
494    /// # struct User {}
495    /// #[derive(Clone, Record, Serialize, Deserialize)]
496    /// struct Edge {
497    ///     description: String,
498    /// }
499    ///
500    /// # #[tokio::main]
501    /// # async fn main() {
502    /// # let db_accessor = DatabaseConnection::builder()
503    /// #     .with_schema_path("tests/schema.yaml")
504    /// #     .apply_schema()
505    /// #     .build().await.unwrap();
506    /// # db_accessor.truncate();
507    /// let user_a = DatabaseRecord::create(User { }, &db_accessor).await.unwrap();
508    /// let user_b = DatabaseRecord::create(User { }, &db_accessor).await.unwrap();
509    ///
510    /// let edge = DatabaseRecord::link(&user_a, &user_b, &db_accessor,
511    ///     Edge { description: "description".to_string() }
512    /// ).await.unwrap();
513    /// assert_eq!(edge.id_from(), user_a.id());
514    /// assert_eq!(edge.id_to(), user_b.id());
515    /// assert_eq!(&edge.description, "description");
516    /// # }
517    /// ```
518    #[maybe_async::maybe_async]
519    pub async fn link<A, B, D>(
520        from_record: &DatabaseRecord<A>,
521        to_record: &DatabaseRecord<B>,
522        db_accessor: &D,
523        edge_record: T,
524    ) -> Result<DatabaseRecord<EdgeRecord<T>>, Error>
525    where
526        A: Record,
527        B: Record,
528        D: DatabaseAccess + ?Sized,
529        T: Record + Send,
530    {
531        let edge = EdgeRecord::new(
532            from_record.id().clone(),
533            to_record.id().clone(),
534            edge_record,
535        )?;
536        DatabaseRecord::create(edge, db_accessor).await
537    }
538
539    /// Retrieves a record from the database with the associated unique `key`
540    ///
541    /// # Arguments:
542    ///
543    /// * `key` - the unique record key as a string slice
544    /// * `db_accessor` - database connection reference
545    ///
546    /// # Returns
547    ///
548    /// On success `Self` is returned,
549    /// On failure an [`Error`] is returned:
550    /// * [`NotFound`] on invalid document key
551    /// * [`UnprocessableEntity`] on data corruption
552    ///
553    /// [`Error`]: crate::Error
554    /// [`NotFound`]: crate::Error::NotFound
555    /// [`UnprocessableEntity`]: crate::Error::UnprocessableEntity
556    #[maybe_async::maybe_async]
557    pub async fn find<D>(key: &str, db_accessor: &D) -> Result<Self, Error>
558    where
559        D: DatabaseAccess + ?Sized,
560    {
561        database_service::retrieve_record(key, db_accessor, T::COLLECTION_NAME).await
562    }
563
564    /// Reloads a record from the database, returning the new record.
565    ///
566    /// # Arguments
567    ///
568    /// * `db_accessor` - database connection reference
569    ///
570    /// # Returns
571    ///
572    /// On success `Self` is returned,
573    /// On failure an [`Error`] is returned:
574    /// * [`NotFound`] on invalid document key
575    /// * [`UnprocessableEntity`] on data corruption
576    ///
577    /// [`Error`]: crate::Error
578    /// [`NotFound`]:crate::Error::NotFound
579    /// [`UnprocessableEntity`]:crate::Error::UnprocessableEntity
580    #[maybe_async::maybe_async]
581    pub async fn reload<D>(self, db_accessor: &D) -> Result<Self, Error>
582    where
583        D: DatabaseAccess + ?Sized,
584        T: Send,
585    {
586        T::find(self.key(), db_accessor).await
587    }
588
589    /// Reloads a record from the database.
590    ///
591    /// # Returns
592    ///
593    /// On success `()` is returned and `self` is updated,
594    /// On failure an [`Error`] is returned:
595    /// * [`NotFound`] on invalid document key
596    /// * [`UnprocessableEntity`] on data corruption
597    ///
598    /// [`Error`]: crate::Error
599    /// [`NotFound`]:crate::Error::NotFound
600    /// [`UnprocessableEntity`]:crate::Error::UnprocessableEntity
601    #[maybe_async::maybe_async]
602    pub async fn reload_mut<D>(&mut self, db_accessor: &D) -> Result<(), Error>
603    where
604        D: DatabaseAccess + ?Sized,
605        T: Send,
606    {
607        *self = T::find(self.key(), db_accessor).await?;
608        Ok(())
609    }
610
611    /// Retrieves all records from the database matching the associated conditions.
612    ///
613    /// # Arguments:
614    ///
615    /// * `query` - The `Query` to match
616    /// * `db_accessor` - database connection reference
617    ///
618    /// # Note
619    ///
620    /// This is simply an AQL request wrapper.
621    ///
622    /// # Returns
623    ///
624    /// On success a `QueryResult` with a vector of `Self` is returned. It can be empty.
625    /// On failure an [`Error`] is returned:
626    /// * [`NotFound`] if no document matches the condition
627    /// * [`UnprocessableEntity`] on data corruption
628    ///
629    /// # Example
630    ///
631    /// ```rust
632    /// # use aragog::query::{Comparison, Filter};
633    /// # use serde::{Serialize, Deserialize};
634    /// # use aragog::{DatabaseConnection, Record, DatabaseRecord};
635    /// #
636    /// # #[derive(Record, Clone, Serialize, Deserialize)]
637    /// # struct User {
638    /// #    username: String,
639    /// #    age: u16,
640    /// # }
641    /// #
642    /// # #[tokio::main]
643    /// # async fn main() {
644    /// # let db_accessor = DatabaseConnection::builder()
645    /// #     .with_schema_path("tests/schema.yaml")
646    /// #     .apply_schema()
647    /// #     .build().await.unwrap();
648    /// # db_accessor.truncate();
649    /// # DatabaseRecord::create(User {username: "RobertSurcouf".to_string() ,age: 18 }, &db_accessor).await.unwrap();
650    /// let query = User::query().filter(Filter::new(Comparison::field("username").equals_str("RobertSurcouf"))
651    ///     .and(Comparison::field("age").greater_than(10)));
652    ///
653    /// // Both lines are equivalent:
654    /// DatabaseRecord::<User>::get(&query, &db_accessor).await.unwrap();
655    /// User::get(&query, &db_accessor).await.unwrap();
656    /// # }
657    /// ```
658    ///
659    /// [`Error`]: crate::Error
660    /// [`NotFound`]:crate::Error::NotFound
661    /// [`UnprocessableEntity`]:crate::Error::UnprocessableEntity
662    #[maybe_async::maybe_async]
663    pub async fn get<D>(query: &Query, db_accessor: &D) -> Result<QueryResult<T>, Error>
664    where
665        D: DatabaseAccess + ?Sized,
666    {
667        query_records(db_accessor, query).await
668    }
669
670    /// Retrieves all records from the database matching the associated conditions in batches.
671    ///
672    /// # Arguments:
673    ///
674    /// * `query` - The `Query` to match
675    /// * `db_accessor` - database connection reference
676    /// * `batch_size`- The maximum number of documents in a batch
677    ///
678    ///
679    /// # Returns
680    ///
681    /// On success a `QueryCursor` is returned. It can be empty.
682    /// On failure an [`Error`] is returned:
683    /// * [`NotFound`] if no document matches the condition
684    /// * [`UnprocessableEntity`] on data corruption
685    ///
686    /// # Example
687    ///
688    /// ```rust
689    /// # use aragog::query::{Comparison, Filter};
690    /// # use serde::{Serialize, Deserialize};
691    /// # use aragog::{DatabaseConnection, Record, DatabaseRecord};
692    /// #
693    /// # #[derive(Record, Clone, Serialize, Deserialize)]
694    /// # struct User {
695    /// #    username: String,
696    /// #    age: u16,
697    /// # }
698    /// #
699    /// # #[tokio::main]
700    /// # async fn main() {
701    /// # let db_accessor = DatabaseConnection::builder()
702    /// #     .with_schema_path("tests/schema.yaml")
703    /// #     .apply_schema()
704    /// #     .build().await.unwrap();
705    /// # db_accessor.truncate();
706    /// # DatabaseRecord::create(User {username: "RobertSurcouf".to_string() ,age: 18 }, &db_accessor).await.unwrap();
707    /// let query = User::query().filter(Filter::new(Comparison::field("age").greater_than(10)));
708    ///
709    /// // Both lines are equivalent:
710    /// DatabaseRecord::<User>::get_in_batches(&query, &db_accessor, 100).await.unwrap();
711    /// User::get_in_batches(&query, &db_accessor, 100).await.unwrap();
712    /// # }
713    /// ```
714    ///
715    /// [`Error`]: crate::Error
716    /// [`NotFound`]:crate::Error::NotFound
717    /// [`UnprocessableEntity`]:crate::Error::UnprocessableEntity
718    #[maybe_async::maybe_async]
719    pub async fn get_in_batches<D>(
720        query: &Query,
721        db_accessor: &D,
722        batch_size: u32,
723    ) -> Result<QueryCursor<T>, Error>
724    where
725        D: DatabaseAccess + ?Sized,
726    {
727        query_records_in_batches(db_accessor, query, batch_size).await
728    }
729
730    /// Retrieves all records from the database matching the associated conditions.
731    ///
732    /// # Arguments:
733    ///
734    /// * `query` - The AQL request string
735    /// * `db_accessor` - database connection reference
736    ///
737    /// # Returns
738    ///
739    /// On success a `QueryResult` with a vector of `Self` is returned. It is can be empty.
740    /// On failure an [`Error`] is returned:
741    /// * [`NotFound`] if no document matches the condition
742    /// * [`UnprocessableEntity`] on data corruption
743    ///
744    /// # Warning
745    ///
746    /// If you call this method on a graph query only the documents that can be serialized into `T` will be returned.
747    ///
748    /// # Example
749    ///
750    /// ```rust
751    /// # use serde::{Serialize, Deserialize};
752    /// # use aragog::{DatabaseConnection, Record, DatabaseRecord};
753    /// #
754    /// # #[derive(Record, Clone, Serialize, Deserialize)]
755    /// # struct User {}
756    /// #
757    /// # #[tokio::main]
758    /// # async fn main() {
759    /// # let db_accessor = DatabaseConnection::builder()
760    /// #     .with_schema_path("tests/schema.yaml")
761    /// #     .apply_schema()
762    /// #     .build().await.unwrap();
763    /// # db_accessor.truncate();
764    /// let query = r#"FOR i in User FILTER i.username == "RoertSurcouf" && i.age > 10 return i"#;
765    ///
766    /// DatabaseRecord::<User>::aql_get(query, &db_accessor).await.unwrap();
767    /// # }
768    /// ```
769    ///
770    /// [`Error`]: crate::Error
771    /// [`NotFound`]:crate::Error::NotFound
772    /// [`UnprocessableEntity`]:crate::Error::UnprocessableEntity
773    #[maybe_async::maybe_async]
774    pub async fn aql_get<D>(query: &str, db_accessor: &D) -> Result<QueryResult<T>, Error>
775    where
776        D: DatabaseAccess + ?Sized,
777    {
778        raw_query_records(db_accessor, query).await
779    }
780
781    /// Creates a new outbound graph `Query` with `self` as a start vertex
782    ///
783    /// # Arguments
784    ///
785    /// * `edge_collection`- The name of the queried edge collection
786    /// * `min` - The minimum depth of the graph request
787    /// * `max` - The maximum depth of the graph request
788    ///
789    /// # Example
790    /// ```rust no_run
791    /// # use serde::{Serialize, Deserialize};
792    /// # use aragog::query::Query;
793    /// # use aragog::{DatabaseConnection, Record};
794    /// #
795    /// # #[derive(Record, Clone, Serialize, Deserialize)]
796    /// # struct User {}
797    /// #
798    /// # #[tokio::main]
799    /// # async fn main() {
800    /// # let db_accessor = DatabaseConnection::builder().build().await.unwrap();
801    /// let record = User::find("123", &db_accessor).await.unwrap();
802    /// // Both statements are equivalent
803    /// let q = record.outbound_query(1, 2, "ChildOf");
804    /// let q = Query::outbound(1, 2, "ChildOf", record.id());
805    /// # }
806    /// ```
807    pub fn outbound_query(&self, min: u16, max: u16, edge_collection: &str) -> Query {
808        Query::outbound(min, max, edge_collection, &self.id)
809    }
810
811    /// Creates a new inbound graph `Query` with `self` as a start vertex
812    ///
813    /// # Arguments
814    ///
815    /// * `edge_collection`- The name of the queried edge collection
816    /// * `min` - The minimum depth of the graph request
817    /// * `max` - The maximum depth of the graph request
818    ///
819    /// # Example
820    /// ```rust no_run
821    /// # use serde::{Serialize, Deserialize};
822    /// # use aragog::query::Query;
823    /// # use aragog::{DatabaseConnection, Record};
824    /// #
825    /// # #[derive(Record, Clone, Serialize, Deserialize)]
826    /// # struct User {}
827    /// #
828    /// # #[tokio::main]
829    /// # async fn main() {
830    /// # let db_accessor = DatabaseConnection::builder().build().await.unwrap();
831    /// #
832    /// let record = User::find("123", &db_accessor).await.unwrap();
833    /// // Both statements are equivalent
834    /// let q = record.inbound_query(1, 2, "ChildOf");
835    /// let q = Query::inbound(1, 2, "ChildOf", record.id());
836    /// # }
837    /// ```
838    pub fn inbound_query(&self, min: u16, max: u16, edge_collection: &str) -> Query {
839        Query::inbound(min, max, edge_collection, &self.id)
840    }
841
842    /// Creates a new outbound graph `Query` with `self` as a start vertex
843    ///
844    /// # Arguments
845    ///
846    /// * `min` - The minimum depth of the graph request
847    /// * `max` - The maximum depth of the graph request
848    /// * `named_graph`- The named graph to traverse
849    ///
850    /// # Example
851    /// ```rust no_run
852    /// # use serde::{Serialize, Deserialize};
853    /// # use aragog::query::Query;
854    /// # use aragog::{DatabaseConnection, Record};
855    /// #
856    /// # #[derive(Record, Clone, Serialize, Deserialize)]
857    /// # struct User {}
858    /// #
859    /// # #[tokio::main]
860    /// # async fn main() {
861    /// # let db_accessor = DatabaseConnection::builder().build().await.unwrap();
862    /// let record = User::find("123", &db_accessor).await.unwrap();
863    /// // Both statements are equivalent
864    /// let q = record.outbound_graph(1, 2, "SomeGraph");
865    /// let q = Query::outbound_graph(1, 2, "SomeGraph", record.id());
866    /// # }
867    /// ```
868    pub fn outbound_graph(&self, min: u16, max: u16, named_graph: &str) -> Query {
869        Query::outbound_graph(min, max, named_graph, &self.id)
870    }
871
872    /// Creates a new inbound graph `Query` with `self` as a start vertex
873    ///
874    /// # Arguments
875    ///
876    /// * `min` - The minimum depth of the graph request
877    /// * `max` - The maximum depth of the graph request
878    /// * `named_graph`- The named graph to traverse
879    ///
880    /// # Example
881    /// ```rust no_run
882    /// # use serde::{Serialize, Deserialize};
883    /// # use aragog::query::Query;
884    /// # use aragog::{DatabaseConnection, Record};
885    /// #
886    /// # #[derive(Record, Clone, Serialize, Deserialize)]
887    /// # struct User {}
888    /// #
889    /// # #[tokio::main]
890    /// # async fn main() {
891    /// # let db_accessor = DatabaseConnection::builder().build().await.unwrap();
892    /// let record = User::find("123", &db_accessor).await.unwrap();
893    /// // Both statements are equivalent
894    /// let q = record.inbound_graph(1, 2, "SomeGraph");
895    /// let q = Query::inbound_graph(1, 2, "SomeGraph", record.id());
896    /// # }
897    /// ```
898    pub fn inbound_graph(&self, min: u16, max: u16, named_graph: &str) -> Query {
899        Query::inbound_graph(min, max, named_graph, &self.id)
900    }
901
902    /// Checks if any document matching the associated conditions exist
903    ///
904    /// # Arguments:
905    ///
906    /// * `query` - The `Query` to match
907    /// * `db_accessor` - database connection reference
908    ///
909    /// # Note
910    ///
911    /// This is simply an AQL request wrapper.
912    ///
913    /// # Returns
914    ///
915    /// On success `true` is returned, `false` if nothing exists.
916    ///
917    /// # Example
918    ///
919    /// ```rust no_run
920    /// # use serde::{Serialize, Deserialize};
921    /// # use aragog::{DatabaseConnection, Record};
922    /// # use aragog::query::{Query, Comparison, Filter};
923    /// #
924    /// # #[derive(Record, Clone, Serialize, Deserialize)]
925    /// # struct User {}
926    /// #
927    /// # #[tokio::main]
928    /// # async fn main() {
929    /// # let db_accessor = DatabaseConnection::builder().build().await.unwrap();
930    /// let query = User::query().filter(
931    ///     Filter::new(Comparison::field("username").equals_str("MichelDu93"))
932    ///         .and(Comparison::field("age").greater_than(10)));
933    /// User::exists(&query, &db_accessor).await;
934    /// # }
935    /// ```
936    #[maybe_async::maybe_async]
937    pub async fn exists<D>(query: &Query, db_accessor: &D) -> bool
938    where
939        D: DatabaseAccess + ?Sized,
940    {
941        let aql = query.aql_str();
942        let aql_query = AqlQuery::new(&aql).batch_size(1).count(true);
943        match db_accessor
944            .database()
945            .aql_query_batch::<Value>(aql_query)
946            .await
947        {
948            Ok(cursor) => cursor.count.map_or(false, |count| count > 0),
949            Err(_error) => false,
950        }
951    }
952
953    /// Getter for the Document `_id` built as `$collection_name/$_key`
954    #[inline]
955    #[allow(clippy::missing_const_for_fn)] // Can't be const in 1.56
956    pub fn id(&self) -> &String {
957        &self.id
958    }
959
960    /// Getter for the Document `_key`
961    #[inline]
962    #[allow(clippy::missing_const_for_fn)] // Can't be const in 1.56
963    pub fn key(&self) -> &String {
964        &self.key
965    }
966
967    /// Getter for the Document `_rev`
968    #[inline]
969    #[allow(clippy::missing_const_for_fn)] // Can't be const in 1.56
970    pub fn rev(&self) -> &String {
971        &self.rev
972    }
973}
974
975#[allow(clippy::used_underscore_binding)]
976impl<T: Record> From<Document<T>> for DatabaseRecord<T> {
977    fn from(doc: Document<T>) -> Self {
978        Self {
979            key: doc.header._key,
980            id: doc.header._id,
981            rev: doc.header._rev,
982            record: doc.document,
983        }
984    }
985}
986
987impl<T: Record> Display for DatabaseRecord<T> {
988    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
989        write!(f, "{} {} Database Record", T::COLLECTION_NAME, self.key())
990    }
991}
992
993impl<T: Record> Deref for DatabaseRecord<T> {
994    type Target = T;
995
996    fn deref(&self) -> &Self::Target {
997        &self.record
998    }
999}
1000
1001impl<T: Record> DerefMut for DatabaseRecord<T> {
1002    fn deref_mut(&mut self) -> &mut Self::Target {
1003        &mut self.record
1004    }
1005}
1006
1007#[cfg(test)]
1008mod tests {
1009    use super::*;
1010
1011    #[test]
1012    fn struct_serialize_deserialize() {
1013        #[derive(Serialize, Deserialize, Clone)]
1014        struct Doc {
1015            a: String,
1016            b: u16,
1017            c: Vec<bool>,
1018        }
1019
1020        let db_record = DatabaseRecord {
1021            key: "key".to_string(),
1022            id: "id".to_string(),
1023            rev: "rev".to_string(),
1024            record: Doc {
1025                a: "a".to_string(),
1026                b: 10,
1027                c: vec![false, true, false],
1028            },
1029        };
1030        let json = serde_json::to_string(&db_record).unwrap();
1031        let parsed_record: DatabaseRecord<Doc> = serde_json::from_str(&json).unwrap();
1032        assert_eq!(&parsed_record.key, &db_record.key);
1033        assert_eq!(&parsed_record.id, &db_record.id);
1034        assert_eq!(&parsed_record.rev, &db_record.rev);
1035        assert_eq!(parsed_record.record.a, db_record.record.a);
1036        assert_eq!(parsed_record.record.b, db_record.record.b);
1037        assert_eq!(parsed_record.record.c, db_record.record.c);
1038    }
1039
1040    #[test]
1041    fn struct_with_enum_serialize_deserialize() {
1042        #[derive(Serialize, Deserialize, Clone)]
1043        struct Doc {
1044            doc: DocEnum,
1045        }
1046
1047        #[derive(Serialize, Deserialize, Clone)]
1048        enum DocEnum {
1049            A { a: String, b: u16, c: Vec<bool> },
1050            B { a: bool, b: f64 },
1051        }
1052
1053        let db_record = DatabaseRecord {
1054            key: "key".to_string(),
1055            id: "id".to_string(),
1056            rev: "rev".to_string(),
1057            record: Doc {
1058                doc: DocEnum::A {
1059                    a: "a".to_string(),
1060                    b: 10,
1061                    c: vec![false, true, false],
1062                },
1063            },
1064        };
1065        let json = serde_json::to_string(&db_record).unwrap();
1066        let parsed_record: DatabaseRecord<Doc> = serde_json::from_str(&json).unwrap();
1067        assert_eq!(&parsed_record.key, &db_record.key);
1068        assert_eq!(&parsed_record.id, &db_record.id);
1069        assert_eq!(&parsed_record.rev, &db_record.rev);
1070        match parsed_record.record.doc {
1071            DocEnum::A { a, b, c } => {
1072                assert_eq!(&a, "a");
1073                assert_eq!(b, 10);
1074                assert_eq!(c, vec![false, true, false]);
1075            }
1076            DocEnum::B { .. } => panic!("Wrong enum variant"),
1077        }
1078    }
1079
1080    #[test]
1081    fn enum_serialize_deserialize() {
1082        #[derive(Serialize, Deserialize, Clone)]
1083        enum DocEnum {
1084            A { a: String, b: u16, c: Vec<bool> },
1085            B { a: bool, b: f64 },
1086        }
1087
1088        let db_record = DatabaseRecord {
1089            key: "key".to_string(),
1090            id: "id".to_string(),
1091            rev: "rev".to_string(),
1092            record: DocEnum::A {
1093                a: "a".to_string(),
1094                b: 10,
1095                c: vec![false, true, false],
1096            },
1097        };
1098        let json = serde_json::to_string(&db_record).unwrap();
1099        let parsed_record: DatabaseRecord<DocEnum> = serde_json::from_str(&json).unwrap();
1100        assert_eq!(&parsed_record.key, &db_record.key);
1101        assert_eq!(&parsed_record.id, &db_record.id);
1102        assert_eq!(&parsed_record.rev, &db_record.rev);
1103        match parsed_record.record {
1104            DocEnum::A { a, b, c } => {
1105                assert_eq!(&a, "a");
1106                assert_eq!(b, 10);
1107                assert_eq!(c, vec![false, true, false]);
1108            }
1109            DocEnum::B { .. } => panic!("Wrong enum variant"),
1110        }
1111    }
1112}