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}