Surreal ORM Documentation
Table of Contents
- Introduction
- Defining Your Data
- Advanced Schema Definitions
- Select Statements
- Advanced Select Queries
- Select Value Statements
- Advanced Select Value Queries
- Running Select Statements
- Running and Returning from a Select Statement
1. Introduction
This document focuses on defining models and using select
and select_value
statements for data retrieval.
2. Defining Your Data
Start by defining a User
struct representing a user in your application.
#![allow(unused)] fn main() { extern crate surreal_orm; use surreal_orm::*; #[derive(Node, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] #[orm(table = "user")] pub struct User { pub id: SurrealSimpleId<Self>, pub account: String, pub friend: String, } }
3. Advanced Schema Definitions
Surreal ORM supports more complex data types including links between different
models. Here's a detailed example using a Student
and a Book
:
#![allow(unused)] fn main() { #[derive(Node, Serialize, Deserialize)] #[orm(table = "student")] pub struct Student { id: SurrealSimpleId<Self>, first_name: String, last_name: String, age: u8, #[orm(link_self = "Student")] best_friend: LinkSelf<Student>, #[orm(link_one = "Book")] fav_book: LinkOne<Book>, #[orm(link_one = "Book")] course: LinkOne<Book>, #[orm(link_many = "Book")] sem_courses: LinkMany<Book>, } #[derive(Node, Serialize, Deserialize)] #[orm(table = "book")] pub struct Book { id: SurrealSimpleId<Self>, content: String, } }
4. Select Statements
select
allows you to construct a SELECT statement to fetch records.
#![allow(unused)] fn main() { use surreal_orm::{*, statements::{order, select}}; let student::Schema { id, first_name, last_name, best_friend, uno_book, course, sem_courses, ref age, .. } = &Student::schema(); let book::Schema { ref content, .. } = Book::schema(); let mut statement = select(arr![age, last_name, content]) .from(Book::table()) .where_( cond(content.like("lowo")) .and(age.greater_than_or_equal(600)) .or(first_name.equal("Oyelowo")) .and(last_name.equal("Oyedayo")), ) .order_by(last_name.desc() .limit(50) .start(20) .timeout(Duration::from_secs(9)) .parallel(); let is_lowo = true; if is_lowo { statement = statement.limit(55).order_by(age.desc()); } }
Using the cond! Macro
In Surreal ORM, while the cond
function provides an elegant way to construct
filters, there's also a macro alternative called cond!
. This macro can offer
more concise and readable representations, especially for complex conditions.
The cond!
macro provides a convenient syntax for constructing filters, similar
to standard Rust conditional expressions. It can handle various operations like
equalities, inequalities, and logical combinations.
Here's a simple example:
#![allow(unused)] fn main() { use surreal_query_builder as surreal_orm; use surreal_orm::*; let age = Field::new("age"); let name = Field::new("name"); let title = Field::new("title"); let filter_simple = cond!(age > 18); let filter_compound = cond!((age > 18) AND (name ~ "%Oyelowo%") OR (title == "Professor")); let filter_mixed = cond!((age.or(4).or(545).or(232)) OR (title = "Professor") AND (age < 100)); }
This macro provides a more intuitive way of writing conditions, especially when
compared to chaining methods. The full definition and capabilities of the
cond!
macro are documented within the Surreal ORM codebase.
5. Advanced Select Queries
You can perform complex queries including nested select statements and conditional query generation. Here is an example:
#![allow(unused)] fn main() { use surreal_orm::{*, statements::{order, select}}; let student::Schema { id, firstName, lastName, bestFriend, unoBook, course, semCoures, ref age, .. } = &Student::schema(); let book::Schema { ref content, .. } = Book::schema(); let ref student_table = Student::get_table(); let ref book_table = Book::get_table(); let ref book_id = thing("book:1").unwrap(); let mut query1 = select(arr![age, lastName, content]) .from(Book::get_table()) .where_( cond(content.like("lowo")) .and(age.greater_than_or_equal(600)) .or(firstName.equal("Oyelowo")) .and(lastName.equal("Oyedayo")), ) .order_by(lastName.desc()) .limit(50) .start(20) .timeout(Duration::from_secs(9)) .parallel(); let statement = select(All) .from(student_table) // .from(&[student_table, book_table]) // .from(book_id) // .from(query1) .where_( cond( (((age + 5) - 6) * 10).greater_then(5) // You can even use raw mathematical operators directly. ) .and(bestFriend.exactly_equal("Oyelowo")) .or(firstName.equal("Oyedayo")) .and(age.greater_than_or_equal(150)), ) .order_by(firstName.rand().desc()) // .order_by(lastName.collate().asc()) // .order_by(id.numeric().desc()) // .group_by(course) // .group_by(firstName) // .group_by(arr![lastName, unoBook]) .start(5) .limit(400) .fetch(firstName) // .fetch(lastName) // .fetch(arr![age, unoBook]) .split(lastName) // .split(firstName) // .split(arr![firstName, semCoures]) .timeout(Duration::from_secs(8)) .parallel(); let is_oyelowo = true; if is_oyelowo { query = query.group_by(arr![age, bestFriend, &Field::new("dayo")]); } }
6. Select Value Statements
select_value
is similar to select
but it only returns the first column from
the result. Here is a basic usage:
#![allow(unused)] fn main() { let statement = select_value(account) .from(user) .where_(account.is("abc")); }
7. Advanced Select Value Queries
You can perform complex value queries as well. Here is an example:
#![allow(unused)] fn main() { let statement = select_value(account) .from(user) .where_( and( account.is("abc"), or( friend.is("xyz"), friend.is("lmn"), ), ), ); let statement = select_value(account) .from(user) .where_( not(account.is("def")), ); }
8. Running Select Statements
Executing a select statement is straightforward. Here's an example that uses
return_many
:
#![allow(unused)] fn main() { extern crate surreal_orm; use surreal_orm::{*, statements::{select, insert}}; #[derive(Node, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] #[orm(table = "weapon")] pub struct Weapon { pub name: String, pub strength: i32, pub created: chrono::DateTime<chrono::Utc>, } let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let generated_weapons = (1..=10) .map(|i| Weapon { name: format!("Weapon {}", i), strength: i * 10, created: chrono::Utc::now(), ..Default::default() }) .collect::<Vec<_>>(); insert(generated_weapons.clone()).run(db.clone()).await?; let ref weapon = Weapon::table(); let weapon::Schema { ref strength, .. } = &Weapon::schema(); let statement = select(All) .from(weapon) .where_( strength.inside( select_value(strength) .from(weapon) .order_by(strength.asc()) .limit(6), ), ) .order_by(strength.desc()) .start(2) .limit(10); assert_eq!( statement.to_raw().build(), "SELECT * FROM weapon WHERE strength INSIDE \ (SELECT VALUE strength FROM weapon ORDER BY strength LIMIT 6) \ ORDER BY strength DESC LIMIT 10 START AT 2;" ); let result = statement.return_many::<Weapon>(db.clone()).await?; assert_eq!(&result[0].name, "Weapon 4"); assert_eq!(&result[1].name, "Weapon 3"); assert_eq!(&result[2].name, "Weapon 2"); assert_eq!(&result[3].name, "Weapon 1"); assert_eq!(result.len(), 4); assert!(result[0].id.to_string().starts_with("weapon:")); Ok(()) }
This example first inserts generated weapon data into the database. Then it
constructs a select
statement and retrieves the weapons whose strength
is in
the top 6, ordered by strength
in descending order, and returns the results
from the third entry. The return_many
function is used to run the statement
and get the result.
9. Running and Returning from a Select Statement
The Surreal ORM package provides the ReturnableSelect
trait that defines
several functions to run a select statement and return results in different
ways. These functions include return_none
, return_first
, return_one
,
return_one_unchecked
, and return_many
.
All these functions run the statement against the SurrealDB database and return results:
return_none
: Returns no result.return_first
: Returns the first result.return_one
: Returns one result.return_one_unchecked
: Returns one result without checking if it's successful.return_many
: Returns many results.run
: Runs the query and provide more flexible deserialization just like surrealdb native drive e.g.run(db).take::<T>(0)
.