Field Traversal
The surreal_orm
library equips developers with powerful field traversal
capabilities, allowing for seamless querying and navigation through the
surrealdb
graph database. This chapter provides an in-depth exploration into
the different traversal methods available and how to harness them effectively.
Basics of Field Traversal
Field traversal is the mechanism used to navigate through a data structure,
pinpointing specific fields or relationships. The design of the surreal_orm
makes traversal not only intuitive but also direct, offering methods to navigate
fields, relationships, and even to apply specific conditions.
To get started, let's set up our environment:
#![allow(unused)] fn main() { use pretty_assertions::assert_eq; use surreal_models::{student, Student}; use surreal_orm::{index, this, where_, All, Buildable, Operatable, SchemaGetter, ToRaw, E}; }
Root Object: The Starting Point
Every traversal starts with the root object. The this()
function is your
gateway, representing the current node or object you're working on.
#![allow(unused)] fn main() { fn basic() { let param_with_path = this(); assert_eq!(param_with_path.to_raw().build(), "$this"); } }
In the code snippet above, the this()
function signifies a reference to the
root object or the primary context of the traversal. When executed, this will
produce "$this"
.
Traversing the Path
Navigating relationships between nodes is where the real power of a graph
database shines. The with_path::<T>(index_or_clause)
method allows you to
specify this path. Here T
is the type of node you're targeting, while
index_or_clause
can either be an index or a clause, such as WHERE age > 18
or E
(an alias for Empty
).
For instance, to get the firstName
of a Student
at index 2
:
#![allow(unused)] fn main() { let param_with_path = this().with_path::<Student>([2]).firstName; }
This traversal, when executed, will output "$this[2].firstName"
.
Direct Field Access within an Object
Sometimes, all you want is to directly access a field within an object. Here's how you can achieve that:
#![allow(unused)] fn main() { fn test_param_simple_clause() { let param_with_path = this().with_path::<Student>(E).lastName; assert_eq!(param_with_path.to_raw().build(), "$this.lastName"); } }
In this example, the alias E
(standing for Empty
) is employed to directly
traverse to the lastName
field of the Student
object.
Direct Field Access within an Array
At other times, you might want to directly access a field within an array:
#![allow(unused)] fn main() { fn test_param_with_path_simple() { let param_with_path = this().with_path::<Student>([2]).firstName; assert_eq!(param_with_path.to_raw().build(), "$this[2].firstName"); } }
Here, the code fetches the firstName
of the Student
located at index 2
.
Deep Relationship Traversal
The true essence of a graph database is revealed when traversing deep relationships. Consider this test:
#![allow(unused)] fn main() { fn test_param_with_path() { let param_with_path = this() .with_path::<Student>([2]) .bestFriend() .bestFriend() .course() .title; assert_eq!(param_with_path.to_raw().build(), "$this[2].bestFriend.bestFriend.course.title"); } }
This test showcases how to navigate through a Student
's best friend's best
friend's course title.
Index function for Indexing
An alternate to square bracket notation [2]
is the index
helper function
e.g(index(2)
):
#![allow(unused)] fn main() { fn test_param_with_path_with_index_square_bracket_variation() { let param_with_path = this() .with_path::<Student>(index(2)) .bestFriend() .bestFriend() .course() .title; assert_eq!(param_with_path.to_raw().build(), "$this[2].bestFriend.bestFriend.course.title"); } }
Traversal with Conditional Clauses
You can also traverse paths with conditions, allowing for more refined querying:
#![allow(unused)] fn main() { fn test_param_with_path_with_clause() { let student::Schema { age, .. } = Student::schema(); let param_with_path = this() .with_path::<Student>(where_(age.greater_than(18))) .bestFriend() .allSemesterCourses([5]) .title; assert_eq!(param_with_path.to_raw().build(), "$this[WHERE age > 18].bestFriend.allSemesterCourses[5].title"); } }
This traversal fetches the title of the fifth semester course of the best friends of students older than 18.
Using the All Wildcard
For scenarios where you want to traverse all items or elements of a certain
relationship or field, the All
wildcard is invaluable:
#![allow(unused)] fn main() { fn test_param_with_path_with_all_wildcard() { let param_with_path = this() .with_path::<Student>(All) .bestFriend() .allSemesterCourses([5]) .title; assert_eq!(param_with_path.to_raw().build(), "$this[*].bestFriend.allSemesterCourses[5].title"); } }
In the traversal above, All
is a wildcard that represents every instance of
the Student
type. The traversal then specifies the fifth course title of all
students' best friends.
Multiple Indexes in Path
There are scenarios where traversing multiple indexed fields or relationships becomes necessary:
#![allow(unused)] fn main() { fn test_param_with_path_multiple_indexes() { let param_with_path = this() .with_path::<Student>([2]) .bestFriend() .allSemesterCourses([5]) .title; assert_eq!(param_with_path.to_raw().build(), "$this[2].bestFriend.allSemesterCourses[5].title"); } }
Here, the traversal first targets the Student
at index 2 and then fetches the
title of the fifth semester course of that student's best friend.
Conclusion
Field traversal in surreal_orm
equips developers with a versatile and powerful
toolset, enabling effective navigation and querying within