Introduction
The landscape of software development is both vast and intricate, dotted with
countless tools and frameworks that rise as answers to the ever-present
challenges faced by developers. Yet, amidst this vastness, finding a tool that
truly resonates with one's philosophies can be like searching for a needle in a
haystack. Such was the experience that led to the creation of surreal_orm
.
After delving into over ten ORM solutions in Rust alone and journeying through
more than twenty across languages such as TypeScript, Python, Java, Ruby,
Elixir, and more, it became starkly evident: while many tools cater to the basic
needs, very few strike the harmonious chord of simplicity, power, and
expressiveness. This realization wasn't just a mere observation—it was the
catalyst that inspired surreal_orm
.
This book is not just an introduction to an ORM library; it's a narrative of a journey, a testament to a set of deeply-held philosophies, and an exploration of groundbreaking innovations:
-
Expressive Yet Intuitive API: Traditional ORMs often make complex queries convoluted and unreadable.
surreal_orm
challenges this norm. At the heart ofsurreal_orm
lies an unwavering commitment to clarity. The belief is straightforward: if a query can be articulated in raw string format, it should be just as elegantly expressible within the ORM. This ensures that even as queries grow intricate, they remain legible, empowering developers to write intuitive code without sacrificing capability. -
Compile-Time Excellence: Harnessing the full might of Rust's compile-time error checking, surreal_orm emphasizes robustness from the onset. From model declarations to query constructions, the goal is to catch potential pitfalls even before the code springs to life. And in scenarios where compile-time checks aren't feasible, surreal_orm employs meticulous runtime validations, standing as a testament to its commitment to reliability and safety.
-
Pioneering Features:
surreal_orm
introduces pioneering ideas that set it apart. From innovative macros such asblock!
,transaction
,object!
,object_partial!
, andcond!
, to compile-time validations of graph structures, and advanced features like deep graph access and auto-parametrized mathematical expressions—these are just a glimpse of the groundbreaking capabilities you'll encounter. -
Full Specification Support: Beyond its intuitive design and innovative features,
surreal_orm
stands tall with its comprehensive support for the full specification. It's not just another ORM or query builder; it's a beacon of compliance and expressiveness in the ORM landscape.
As you delve deeper into these pages, you'll journey beyond the mechanics,
delving into the essence of surreal_orm
—understanding its origins, the
problems it seeks to solve, and the philosophies that molded its creation. It's
a tale of refusing to settle, of reimagining boundaries, and of sculpting a
solution when none seemed just right.
Each chapter, carefully crafted by the very creator of surreal_orm
, promises a
deep dive into its intricacies, philosophies, and innovations. So, whether
you're a seasoned Rust developer, an ORM enthusiast, or a curious soul eager to
explore the intersections of innovation and software development, this book
promises a voyage into the heart of data management with a fresh perspective in
Rust—a realm where convention meets innovation, culminating in the creation of
something truly surreal.
-- © Oyelowo Oyedayo, 2023.
Quick Start
Surreal ORM Documentation
Introduction
Surreal ORM is an Object-Relational Mapping and query-building library for Rust that provides a high-level API for interacting with SurrealDB, a distributed graph database. This documentation will guide you through the usage and features of the Surreal ORM library.
Getting Started
To use Surreal ORM in your Rust project, you need to add it as a dependency in
your Cargo.toml
file:
[dependencies]
surreal_orm = "https://github.com/Oyelowo/surreal_orm"
After adding the dependency, you can import the necessary modules in your Rust code:
#![allow(unused)] fn main() { use surreal_orm::*; }
Connecting to SurrealDB
Before interacting with SurrealDB, you need to establish a connection to the database. The following example demonstrates how to create a connection to a local SurrealDB instance:
use surrealdb::engine::local::Mem; use surrealdb::Surreal; #[tokio::main] async fn main() { let db = Surreal::new::<Mem>(()).await.unwrap(); }
In this example, we create a new SurrealDB instance using the Surreal::new
function with the local::Mem
engine. The local::Mem
engine represents a
local in-memory database. You can replace it with other engine types according
to your setup.
Defining a Model
A model in Surreal ORM represents a database table. You can define a model by
creating a Rust struct and implementing the Node
or Edge
trait. Here's an example of
defining a SpaceShip
model:
#![allow(unused)] fn main() { use surreal_orm::*; #[derive(Node, Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] #[orm(table = space_ship)] pub struct SpaceShip { pub id: SurrealSimpleId<Self>, pub name: String, pub age: u8, } }
In this example, we define a SpaceShip
struct and annotate it with the Model
derive macro. The table
attribute specifies the name of the corresponding
database table.
Querying Data
Surreal ORM provides a fluent and expressive API for querying data from the
database. You can use the select
function to start a select statement and
chain various methods to build the query. Here's an example:
#![allow(unused)] fn main() { use surreal_orm::statements::{select, All}; let space_ship::Schema { name, age, .. } = SpaceShip::schema(); let statement = select(All) .from(space_ship) .where_(name.equal("Millennium Falcon")) .order_by(age.desc()) .limit(10); }
In this example, we start a select statement using the select
function and
pass the All
argument to select all fields. We specify the table name using
the from
method and add a condition using the where_
method. We can also use
the order_by
method to specify the sorting order and the limit
method to
limit the number of results.
Inserting Data
To insert data into the database, you can use the insert
function and provide
the data as a vector of structs. Here's an example:
#![allow(unused)] fn main() { use surreal_orm::statements::insert; let spaceships = vec![ SpaceShip { id: "1".to_string(), name: "Millennium Falcon".to_string(), age: 79, }, SpaceShip { id: "2".to_string(), name: "Starship Enterprise".to_string(), age: 15, }, ]; insert(spaceships).return_many(db.clone()).await?; }
In this example, we define a vector of SpaceShip
structs and pass it to the
insert
function. We then call the run
method to execute the insertion
operation.
Updating Data
To update data in the database, you can use the update
function and provide
the updated data as a struct. Here's an example:
#![allow(unused)] fn main() { use surreal_orm::statements::update; let spaceship = SpaceShip { id: "1".to_string(), name: "Millennium Falcon".to_string(), age: 60 }; update(spaceship).run(db.clone()).await?; }
In this example, we define a SpaceShip
struct with the updated data and pass
it to the update
function. We then call the run
method to execute the update
operation.
Deleting Data
To delete data from the database, you can use the delete
function and provide
the condition for deletion. Here's an example:
#![allow(unused)] fn main() { use surreal_orm::statements::{delete, Field}; let space_ship::Schema { name, age, .. } = SpaceShip::schema(); let condition = name.eq("Millennium Falcon"); delete(space_ship) .where_(cond(name.equal("Millennium Falcon")).and(age.less_then(50))) .run(db.clone()) .await?; }
In this example, we use the delete
function and specify the table name as a
string. We add a condition using the where_
method, and then call the run
method to execute the deletion operation.
Conclusion
This concludes the basic usage and features of the Surreal ORM library. You can explore more advanced features and methods in the API documentation. If you have any further questions or need assistance, feel free to reach out.
Comparision
Date Model
Data Types
Data Model in surreal_orm
In the surreal_orm
, developers are provided with a comprehensive data model
that mirrors the specifications laid out by the SurrealDB documentation. This
ensures seamless integration with SurrealDB while also extending the
capabilities to cater to more advanced use cases, such as supporting diverse
value types in one unified representation.
Table of Contents
Overview
The data model in surreal_orm
allows for a flexible representation of
different data types. By utilizing structures such as ValueType
, the ORM can
represent a wide array of types from basic values, fields, parameters, to
complex operations and statements.
Record IDs
While the official SurrealDB documentation might detail how unique identifiers
are managed for records, the ORM's handling of this might be implicit or handled
in a way that abstracts the details away from the developer. You can read more
on a dedicated chapter to Surreal Id
where an abstraction is created to make
it a easier, more intuitive and consistent to work with record ids in surrealdb.
Basic Types
Strings
In surreal_orm
, strings are represented using the StrandLike
structure:
#![allow(unused)] fn main() { pub struct StrandLike(..); }
This struct can be used to represent a string value, field, or parameter, allowing it to be seamlessly integrated into various parts of a query.
Numbers
Numbers are represented using the NumberLike
structure:
#![allow(unused)] fn main() { pub struct NumberLike(..); }
Like StrandLike
, it can be used to represent a numeric value, field, or
parameter in a query.
Datetimes
Datetimes are encapsulated using the DatetimeLike
structure:
#![allow(unused)] fn main() { pub struct DatetimeLike(..); }
This allows for a clear representation of date and time values within the ORM.
Objects
Objects are complex data types that encapsulate key-value pairs. They are
represented in surreal_orm
using the ObjectLike
structure:
#![allow(unused)] fn main() { pub struct ObjectLike(..); }
Arrays
Arrays, which can contain multiple items of the same type, are represented using
the ArrayLike
structure:
#![allow(unused)] fn main() { pub struct ArrayLike(..); }
And for function arguments, the ArgsList
structure is used:
#![allow(unused)] fn main() { pub struct ArgsList(..); }
Geometries
Geometries, which might represent spatial data, are encapsulated in the
GeometryLike
structure:
#![allow(unused)] fn main() { pub struct GeometryLike(..); }
Record Links
While the provided code does not show explicit handling for record links, it can
be inferred that such links could be represented using SurrealId
types.
This is a foundational overview of the data model in surreal_orm
, with the aim
of mirroring the SurrealDB specifications. The ORM extends the basic data types
to provide a richer experience, supporting various operations and query
constructs seamlessly.
Future
In surrealdb
, futures provide a powerful mechanism to compute dynamic values
when data is selected and returned to the client. Essentially, a future is a
type of cast function that enables values to be dynamically evaluated upon
retrieval.
Table of Contents
Introduction to Futures
Futures are a unique feature of SurrealDB that allows for dynamic computation of values. Instead of storing a static value within a record, futures compute the value dynamically whenever the record is accessed. This ensures that you always get the most recent and relevant data.
Simple Futures
Any value or expression can be wrapped inside a future, ensuring it's evaluated upon every access.
** Example **
#![allow(unused)] fn main() { let result = create().set(object!(Person { accessed_date: future(time::now!()) }); assert_eq!(result.build(), "CREATE person SET accessed_date = <future> { time::now() }"); }
Futures Depending on Other Fields
Futures can also compute values based on other fields in the record. This allows for dynamic calculations that reflect the latest state of the record.
** Example **
#![allow(unused)] fn main() { let birthday = Person::schema().birthday; let eighteen_years = Duration::from_secs(60 * 60 * 24 * 7 * 365 * 18); let date_of_birth = chrono::Date::MIN_UTC; let can_drive = future("time::now() > birthday + 18y"); let result = create().set(object!(Person { birthday: date_of_birth, can_drive: future(time::now!().gt(birthday).plus(eighteen_years)) })); assert_eq!(result.build(), "CREATE person SET birthday = 2007-06-22, can_drive = <future> { time::now() > birthday + 18y }"); }
Advanced Usage of Futures
Futures offer much more than just simple dynamic calculations. They can dynamically access remote records, execute subqueries, and even traverse graphs.
** Example **
#![allow(unused)] fn main() { let friends = Person::schema().friends; let id1 = Person::create_id("dayo"); let id2 = Person::create_id("yelow"); let friends = Person::schema().friends; let result = create().set(object!(Person { name: String::from("Oyelowo"), friends: vec![id1, id2], adult_friends: future(friends(cond(age.gt(18))).name), })); assert_eq!(result.build(), "CREATE person SET name = 'Oyelowo', friends = [person:dayo, person:yelow], adult_friends = <future> { friends[WHERE age > 18].name }"); }
Utilizing futures in surreal_orm
provides a dynamic layer to your data,
ensuring that you always receive the most up-to-date calculations and
evaluations when querying your records. Whether you're calculating age, fetching
related records, or even performing complex graph operations, futures have got
you covered.
Casting
Casting is an indispensable tool in data management, allowing developers to
convert values from one type to another. This chapter provides an in-depth look
into the casting functionality provided by surreal_orm
, illuminating its
power, elegance, and strict adherence to the SurrealDB specifications.
Table of Contents
Introduction to Casting
In programming, casting is the practice of converting variables from one type to another, enabling more flexible data manipulation. Whether receiving input from a user, reading data from a file, or interfacing with databases, casting becomes a pivotal component.
Casting to Boolean
This function converts a value into a boolean. In raw queries, it's represented
as <bool>
.
#![allow(unused)] fn main() { let result = bool("true"); assert_eq!(result.build(), "<bool> true"); }
Casting to Integer
Convert a value into an integer. In raw queries, it's represented by <int>
.
#![allow(unused)] fn main() { let result = int(13.572948467293847293841093845679289); assert_eq!(result.build(), "<int> 13"); }
Casting to Float
Convert a value into a floating point number. In raw queries, it's represented
by <float>
.
#![allow(unused)] fn main() { let result = float(13.572948467293847293841093845679289); assert_eq!(result.build(), "<float> 13.572948467293847"); }
Casting to String
Convert a value into a string. In raw queries, it's represented by <string>
.
#![allow(unused)] fn main() { let result = string(true); assert_eq!(result.build(), "<string> true"); }
Casting to Number
Convert a value into an infinite precision decimal number. In raw queries, it's
represented by <number>
.
#![allow(unused)] fn main() { let result = number(13.572948467293847293841093845679289); assert_eq!(result.build(), "<number> 13.572948467293847293841093845679289"); }
Casting to Decimal
Convert a value into an infinite precision decimal number. In raw queries, it's
represented by <decimal>
.
#![allow(unused)] fn main() { let result = decimal(13.572948467293847293841093845679289); assert_eq!(result.build(), "<decimal> 13.572948467293847293841093845679289"); }
Casting to DateTime
Convert a value into a datetime. In raw queries, it's represented by
<datetime>
.
#![allow(unused)] fn main() { let result = datetime("2022-06-07 will be parsed"); assert_eq!(result.build(), "<datetime> 2022-06-07"); }
Casting to Duration
Convert a value into a duration. In raw queries, it's represented by
<duration>
.
#![allow(unused)] fn main() { let result = duration("1h30m will be parsed"); assert_eq!(result.build(), "<duration> 1h30m"); }
Conclusion
Surreal Orm presents a powerful and user-friendly approach to casting, adhering closely to SurrealDB standards. Whether you're an experienced Rust developer or just starting, surreal_orm provides the tools for precise and effortless data manipulation.
Concepts
Model
In Surreal, a Model represents a blueprint of your data model consisting of various Nodes and Edges. A Model is a collection of various Nodes (entities) and their relationships (Edges), providing a comprehensive view of your data.
The Object struct is used to define a Model, and it has its own set of struct
and field attributes. For instance, the rename_all
struct attribute lets you
define a case convention for all the fields in the Model. And the rename
field
attribute allows you to specify a different name for a field.
Node
In Surreal, your database is represented using Nodes, Edges, and Objects:
-
Nodes: These correspond to database tables, defined as Rust structs implementing the
Node
trait. Nodes can link to other Nodes and incorporate Objects for complex nested data structures. -
Edges: Edges represent relationships between Nodes and are used for modeling many-to-many relationships or storing additional information about the relationship itself.
-
Objects: These are complex nested data structures embedded within Nodes. While they don't represent standalone tables, they facilitate complex data modeling within a Node.
Nodes are the heart of your database model in Surreal. They're Rust structs
decorated with Node
attributes for overall configuration and field-specific
attributes for property definition. There are three types of links that you can
use to define relationships between Nodes: LinkSelf
, LinkOne
, and
LinkMany
.
-
LinkSelf
: This is a self-referential link within the same Node (table). For example, if anAlien
can be friends with other aliens, you would useLinkSelf
. -
LinkOne
: This creates a one-to-one relationship between two different Nodes. If everyAlien
has exactly oneWeapon
, you would useLinkOne
. -
LinkMany
: This creates a one-to-many relationship between two Nodes. If anAlien
can have multipleSpaceShip
s, you would useLinkMany
.
For example:
#![allow(unused)] fn main() { use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use surreal_orm::{LinkMany, LinkOne, LinkSelf, SurrealSimpleId, Node}; #[derive(Node, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] #[orm(table = "alien")] pub struct Alien { pub id: SurrealSimpleId<Self>, #[orm(link_self = "Alien")] pub friend: LinkSelf<Alien>, #[orm(link_one = "Weapon")] pub weapon: LinkOne<Weapon>, #[orm(link_many = "SpaceShip")] pub space_ships: LinkMany<SpaceShip>, } #[derive(Node, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] #[orm(table = "weapon")] pub struct Weapon { pub id: SurrealSimpleId<Self>, pub name: String, pub strength: u64, } #[derive(Node, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[orm(table = "space_ship")] pub struct SpaceShip { pub id: SurrealId<Self, String>, pub name: String, pub created: DateTime<Utc>, } }
In this Alien
Node, an alien can have an friend (another alien), a weapon
(one-to-one relationship with Weapon
Node), and multiple spaceships
(one-to-many relationship with SpaceShip
Node).
In summary, Nodes in Surreal provide a powerful way to model your database schema directly in Rust, with type safety, automatic serialization/deserialization, and the ability to define complex relationships between different tables.
Node Attributes on Struct
In Surreal ORM, node attributes provide a convenient mechanism to dictate the behavior and structure of database tables and their associated fields. These attributes are not only powerful tools for developers but also help in maintaining a consistent and clear database schema. This chapter will delve into the intricacies of node attributes, their application, and best practices for their usage.
Table of Contents
- Introduction to Node Attributes
- Working with Node Attributes
- Node Attributes: Examples
- Ensuring Valid Usage of Node Attributes
- Conclusion
Introduction to Node Attributes
Node attributes in Surreal ORM allow developers to:
- Rename fields of a struct according to a naming convention.
- Explicitly set or infer the table name.
- Enforce schema structures.
- Handle table drops and recreations.
- Create table projections or views.
- Set granular permissions for CRUD operations on tables.
- Define the table structure either inline or through external functions.
Working with Node Attributes
Supported table attributes
Struct Attributes
Attribute | Description | Type | Optional |
---|---|---|---|
rename_all | Renames all the struct's fields according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y |
table | Explicitly define the table name. By default, it must correspond with the struct name in snake_case. Use relax_table if you want to opt out of this but not encouraged. | Option | Y |
relax_table | Determines whether the struct's name is matched to the table name as the snake case by default. This is not encouraged. Using your struct 1:1 to your database tables helps to ensure uniquness and prevent confusion. | Option | Y |
schemafull | Make the table enforce a schema struct. | Option | Y |
drop | Drop the table if it exists and create a new one with the same name. | Option | Y |
as | Inline statement e.g select(All).from(user) for creating a projection using the DEFINE TABLE statement. This is useful for copying data from an existing table in the new table definition. This is similar to making a view in a RDBMS. | A select statement | Y |
as_fn | Same as above as but defined as external function from the struct e.g select_reading_from_user for creating a projection using the DEFINE TABLE statement. This is useful for copying data from an existing table in the new table definition. This is similar to making a view in a RDBMS. | A function name | Y |
permissions | Specify permissions that apply to the table using the for statement. | ForStatement | Y |
permissions_fn | Same as permission but as an external function from the struct. Specify permissions that apply to the table using the for statement. | ForStatement | Y |
define | Generates a DEFINE TABLE statement for the table. This overrides other specific definitions to prevent confusion and collision. You can also invoke an external function directly rather than inlining the function e.g define = "define_student()" | inline code string | Y |
define_fn | Generates a DEFINE TABLE statement for the table. This overrides other specific definitions to prevent confusion and collision. Same as define attribute but expects the function name instead rather than invocation i.e define_student instead of define_student() . You can also invoke an external function directly rather than inlining the function e.g `define = "def |
Node Attributes: Examples
Auto-Inferred Table Name
By default, the ORM auto-infers the table name from the struct's name. For a
struct named Alien
, the table name would be inferred as alien
.
#![allow(unused)] fn main() { #[derive(Node, Serialize, Deserialize)] pub struct Alien { id: SurrealSimpleId<Self>, } }
The corresponding table definition would be:
DEFINE TABLE alien;
Explicit Table Name
You can explicitly set the table name using the table
attribute. By
default, the table name should be the snake case of the struct name. This is to
ensure consistency and uniqueness of table model struct. If you want a name
other than the snake case version, you need to add the attribute -
relax_table
:
#![allow(unused)] fn main() { #[derive(Node, Serialize, Deserialize)] #[orm(table = "student_test")] pub struct StudentTest { id: SurrealSimpleId<Self>, } }
The corresponding table definition would be:
DEFINE TABLE student_test;
Using define
for Inline Table Definition
The define
attribute allows for inline table definitions, either through an
inline expression or an invoked external function.
#![allow(unused)] fn main() { #[derive(Node, Serialize, Deserialize)] #[orm(table = "student_test_4", as_ = "select(All).from(Student::table())", define = "define_student()")] pub struct StudentTest4 { id: SurrealSimpleId<Self>, } }
Using define_fn
for External Function Definition
Alternatively, the define_fn
attribute points to an external function to
define the table:
#![allow(unused)] fn main() { #[derive(Node, Serialize, Deserialize)] #[orm(table = "student_test_7", define_fn = "define_student")] pub struct StudentTest7 { id: SurrealSimpleId<Self>, } }
Specifying Permissions
The permissions
attribute allows you to set granular permissions for CRUD
operations. This takes Permissions
struct. Therefore, if you are using an
external function, it has to return Permissions
which is then invoked and
passed in:
#![allow(unused)] fn main() { #[derive(Node, Serialize, Deserialize)] #[orm(table = "student_test_5", permissions = "student_permissions()")] pub struct StudentTest5 { id: SurrealSimpleId<Self>, } }
In the example above, the student_permissions()
function would define
permissions using the for
statement from Surreal orm. for
returns
Permissions
.
Ensuring Valid Usage of Node Attributes
While node attributes are powerful and flexible, their misuse can lead to unexpected behaviors. Thankfully, the ORM actively checks for invalid usages and ensures that developers don't misuse these attributes. Here are some guidelines and checks enforced by the ORM to avoid pitfalls:
Conflicting Definitions:
define
vsdefine_fn
: Using bothdefine
anddefine_fn
attributes on the same struct is not allowed . Only one should be present to define the table.
#![allow(unused)] fn main() { #[derive(Node, Serialize, Deserialize)] #[orm(table = "student_test_6", define_fn = "define_student", define = "define_student()")] pub struct StudentTest6 { id: SurrealSimpleId<Self>, } }
The ORM will raise an error for such definitions, ensuring clarity and preventing conflicts.
-
as
vsas_fn
: Only one of these should be used to define projections or views. -
permissions
vspermissions_fn
: These attributes shouldn't coexist on the same struct, choose one based on your need. -
value
vsvalue_fn
andassert
vsassert_fn
: Similar to the above, only one of these pairs should be present on a struct.
Avoid Excessive Attributes with define
or define_fn
:
When using define
or define_fn
, ensure no other attributes are present
except table
and relax_table
.
Consistent Table Naming:
By default, the table name should be the snake case of the struct name. This is
to ensure consistency and uniqueness of table model struct. If you want a name
other than the snake case version, you need to add the attribute -
relax_table
.
Using Functions for Attributes:
When using attributes that invoke functions, such as
define = "define_student()"
, ensure that the invoked function returns the
appropriate type. For instance, define_student()
should return a
DefineStatement
struct, and student_permissions()
should return
Permissions
.
Conclusion
By following these guidelines and the checks enforced by the ORM, developers can ensure a smooth and error-free database definition process. Remember, while the ORM provides these checks, it's always a good practice for developers to validate and review their implementations to guarantee best practices and avoid potential pitfalls.
Chapter: Node Field Attributes
Table of Contents
- Introduction
- Basic Annotations
- Granular Attributes
- Defining Attributes with Functions
- Field Definitions
- Links and Relationships
- Customizing Behavior with Inline Expressions
- Invalid Usages
- Summary and Conclusion
1. Introduction
Field attributes in Surreal orm allow developers to fine-tune the behavior and characteristics of each field within a database node. As you've already seen in the table of attributes, each attribute serves a specific purpose. In this chapter, we'll delve deeper into each attribute, providing examples and clarifying common misconceptions.
Attributes Table
Field Attributes
Attribute | Description | Type | Optional |
---|---|---|---|
rename | Renames the field. | string | Y |
link_one | Specifies a relationship to a singular record in another node table in the database. | model=NodeEdgeNode, connection ->edge->node | Y |
link_self | Specifies a relationship to a singular record in the same node table in the database. | Node | Y |
link_many | Specifies a relationship to multiple records in another node table in the database. | `Vec<S | |
relate | Generates the relation helpers for the Current Node struct to an edge and destination node. The corresponding field name is merely used as an alias in code generation and is read only and not serializable. e.g student:1->writes->book:2 | ||
type | Specify the valid surrealdb field's type. One of any, array, bool, datetime, decimal, duration, float, int, number, object, string, record. | surrealdb field type | Y |
assert | Assert the field's value meets a certain criteria using the an filter using value() function as an operation (e.g value().is_not(NONE) ) or in cond helper function for more complex filter assertion. e.g cond(value().is_not(NONE)).and(value().like("@codebreather")) . | inline code string | Y |
assert_fn | Provide a function to assert the field's value meets a certain criteria. This is similar to assert but is intended for an already created external function which is useful when reusing an assertion e.g is_email . | function name string | Y |
item_type | Only when for nested array. Specifies the type of the items of the array. | Option<FieldTypeWrapper> | Y |
item_assert | Only used for nested array. Asserts a condition on the content. | Option<syn::LitStr> | Y |
item_assert_fn | Only used for nested array. Specifies the function to assert a condition on the content. | Option<syn::Path> | Y |
define | Generates a DEFINE FIELD statement for the table. This overrides other specific definitions to prevent confusion and collision. You can also invoke an external function directly rather than inlining the function e.g define = "define_age()" | inline code string | Y |
define_fn | Generates a DEFINE FIELD statement for the table. This overrides other specific definitions to prevent confusion and collision. Same as define attribute but expects the function name instead rather than invocation i.e define_age instead of define_age() . You can also invoke an external function directly rather than inlining the function e.g `define = "def | ||
skip_serializing | When true, this field will be omitted when serializing the struct. | bool | Y |
2. Basic Annotations
Let's begin with a basic example. The Student
struct below uses minimal
annotations:
#![allow(unused)] fn main() { #[orm(table = "student")] pub struct Student { id: SurrealId<Student, String>, first_name: String, last_name: String, age: u8, } }
Here:
table
determines the name of the table in the database that corresponds to this struct.
3. Granular Attributes
For a more detailed configuration of a field, you can use granular attributes.
The Student
struct provides various usages:
#![allow(unused)] fn main() { #[orm( table = "student", permissions = "student_permissions()", )] pub struct Student { id: SurrealId<Student, String>, first_name: String, last_name: String, #[orm( type_ = "int", value = "18", assert = "cond(value().is_not(NONE)).and(value().gte(18))", permissions = "age_permissions()" )] age_inline_expr: u8, // ... other fields ... } }
Here:
type
specifies the data type of the field in the database.value
sets a default value for the field.assert
provides a condition that the field value must satisfy.permissions
specifies what operations can be performed on the field and under what conditions.
4. Defining Attributes with Functions
You can externalize the logic for defining attributes by using external functions. This aids in reusability and cleaner code:
#![allow(unused)] fn main() { #[orm( table = "student_with_define_fn_attr", define_fn = "define_student_with_define_attr" )] pub struct StudentWithDefineFnAttr { // ... fields ... #[orm(type_ = "int", define_fn = "age_define_external_fn_path")] age_define_external_fn_path: u8, } }
Here:
define_fn
allows you to specify an external function that returns the definition of the table or field.
5. Field Definitions
Fields can be defined in multiple ways using surreal_orm
:
Inline Definitions:
#![allow(unused)] fn main() { #[orm(type_ = "int", value = "18")] age: u8, }
External Function Invoked:
#![allow(unused)] fn main() { #[orm(type_ = "int", value = "get_age_default_value()")] age_default_external_function_invoked_expr: u8, }
Using External Function Attributes:
#![allow(unused)] fn main() { #[orm(type_ = "int", value_fn = "get_age_default_value")] age_external_fn_attrs: u8, }
Mixing and Matching:
#![allow(unused)] fn main() { #[orm(type_ = "int", value = "get_age_default_value()", assert_fn = "get_age_assertion")] age_mix_and_match_external_fn_inline_attrs: u8, }
6. Links and Relationships
You can define relationships between different structs (representing tables in
the database). Relationships can be one-to-one
, one-to-many
, or
many-to-many
.
For instance:
#![allow(unused)] fn main() { #[orm(link_one = "Book")] fav_book: LinkOne<Book>, }
This indicates a one-to-one relationship between a student and a book.
7. Customizing Behavior with Inline Expressions
In surreal_orm
, you can use inline expressions to add custom behavior:
#![allow(unused)] fn main() { #[orm( type_ = "int", value = "get_age_by_group_default_value(AgeGroup::Teen)", assert = "get_age_assertion()", )] age_teen_external_function_invoked_expr: u8, }
Here, the default value of age_teen_external_function_invoked_expr
is
determined by the get_age_by_group_default_value
function with
AgeGroup::Teen
as a parameter.
8. Invalid Usages
When using surreal_orm
, it's essential to be cautious about the attributes you
combine. Certain combinations are considered invalid and will result in
compilation errors.
1. Mixing value
and value_fn
:
These two attributes are mutually exclusive. You can't define a default value using both a direct expression and a function at the same time.
#![allow(unused)] fn main() { #[orm( type_ = "int", value = "get_age_default_value()", value_fn = "get_age_default_value" )] age: u8, }
2. Mixing assert
and assert_fn
:
Similarly, you can't use both an inline assertion and an external function for the same purpose.
#![allow(unused)] fn main() { #[orm( type_ = "int", assert = "get_age_assertion()", assert_fn = "get_age_assertion" )] age: u8, }
3. Mixing permissions
and permissions_fn
:
Permissions should be defined either inline or through an external function, but not both.
#![allow(unused)] fn main() { #[orm( type_ = "int", permissions = "age_permissions()", permissions_fn = "age_permissions" )] age: u8, }
4. Combining define
and define_fn
:
These attributes are also mutually exclusive. When specifying a custom definition, you should use either an inline expression or an external function.
#![allow(unused)] fn main() { #[orm( type_ = "int", define = "define_age()", define_fn = "define_age" )] age: u8, }
5. Using other attributes with define
or define_fn
:
When you use either the define
or define_fn
attribute, you cannot use any
other attributes (except for type
). This is because the definition provided
should be comprehensive and not require additional modifiers.
For example, the following combinations are invalid:
#![allow(unused)] fn main() { #[orm( type_ = "int", value = "18", define = "define_age()" )] age: u8, }
#![allow(unused)] fn main() { #[orm( type_ = "int", assert = "cond(value().is_not(NONE)).and(value().gte(18))", define = "define_age()" )] age: u8, }
#![allow(unused)] fn main() { #[orm( type_ = "int", permissions = "for_permission([CrudType::Create, CrudType::Delete]).where_(StudentTest3::schema().firstName.is(\"Oyelowo\"))", define = "define_age()" )] age: u8, }
By being aware of these restrictions and avoiding the invalid combinations, you can ensure that your code remains consistent, clear, and free from compilation errors.
9. Summary and Conclusion
With surreal_orm
, you can easily map Rust structs to database tables,
customize field properties, define relationships, and more. This provides a
powerful way to interact with databases in a type-safe manner while keeping the
codebase clean and maintainable.
For a hands-on illustration, consider the following code snippet which provides a comprehensive overview of the various annotations:
#![allow(unused)] fn main() { #[derive(Node, TypedBuilder, Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] #[orm( table = "student_with_granular_attributes", drop, schemafull, as_ = "select(All).from(Student::table())", permissions = "student_permissions()", )] pub struct StudentWithGranularAttributes { id: SurrealId<StudentWithGranularAttributes, String>, first_name: String, last_name: String, #[orm( type_ = "int", value = "18", assert = "cond(value().is_not(NONE)).and(value().gte(18))", permissions = "for_permission([CrudType::Create, CrudType::Delete]).where_(StudentWithGranularAttributes::schema().firstName.is(\"Oyelowo\"))" )] age_inline_expr: u8, // ... other fields ... } }
This chapter is a starting point to dive deeper into surreal_orm
. With this
foundation, you can explore more advanced features and best practices to make
the most of this powerful ORM crate in Rust.
Edge
Edges in Surreal represent relationships between Nodes. They are useful when you
want to model many-to-many relationships or when you want to store additional
information about the relationship itself. Edges can be seen as "relationship
tables" in a relational database context, holding metadata about the
relationship between two entities. Edges are defined by a Rust struct that
implements the Edge
trait.
Here's a detailed example:
#[derive(Node, Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
#[orm(table = "alien")]
pub struct Alien {
pub id: SurrealSimpleId<Self>,
pub name: String,
// This is a read-only field
#[orm(relate(model = "AlienVisitsPlanet", connection = "->visits->planet"))]
#[serde(skip_serializing, default)]
pub planets_to_visit: Relate<Planet>,
}
#[derive(Node, Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "camelCase")]
#[orm(table = "planet")]
pub struct Planet {
pub id: SurrealSimpleId<Self>,
pub population: u64,
}
// Visits
#[derive(Edge, Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "camelCase")]
#[orm(table = "visits")]
pub struct Visits<In: Node, Out: Node> {
pub id: SurrealSimpleId<Self>,
#[serde(rename = "in")]
pub in_: LinkOne<In>,
pub out: LinkOne<Out>,
pub time_visited: Duration,
}
// Connects Alien to Planet via Visits
pub type AlienVisitsPlanet = Visits<Alien, Planet>;
The Alien
Node has a field planets_to_visit
which is of type
Relate<Planet>
. This field doesn't represent a direct link from Alien
to
Planet
. Instead, it represents an indirect relationship via the Visits
Edge.
This indirect relationship is defined by the Relate
annotation on the
planets_to_visit
field in the Alien
Node.
The
#[orm(relate(model = "AlienVisitsPlanet", connection = "->visits->planet"))]
attribute on the planets_to_visit
field in the Alien
Node tells Surreal that
this field represents the Planet
Nodes that are connected to the Alien
Node
via the AlienVisitsPlanet
Edge. The connection = "->visits->planet"
part
defines the path of the relationship from the Alien
Node, through the Visits
Edge (represented by "visits"), and finally to the Planet
Node.
The Visits
Edge struct defines the structure of this relationship. It
implements Edge
and specifies two type parameters: In
and Out
which
represent the source and target Node types of the relationship, respectively. In
this example, Alien
is the source and Planet
is the target. The Visits
Edge also has a time_visited
field, which can store additional information
about each visit.
In summary, Surreal Edges provide a flexible way to model complex relationships
between Nodes, such as when an Alien
visits a Planet
. They allow for
relationships to be modeled with additional information (like the time_visited
field in the Visits
Edge) and can represent both direct and indirect
connections between Nodes.
Struct Attributes
Attribute | Description | Type | Optional |
---|---|---|---|
rename_all | Renames all the struct's fields according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y |
table | Explicitly define the table name. By default, it must correspond with the struct name in snake_case. Use relax_table if you want to opt out of this but not encouraged. | Option | Y |
relax_table | Determines whether the struct's name is matched to the table name as the snake case by default. This is not encouraged. Using your struct 1:1 to your database tables helps to ensure uniquness and prevent confusion. | Option | Y |
schemafull | Make the table enforce a schema struct. | Option | Y |
drop | Drop the table if it exists and create a new one with the same name. | Option | Y |
as | Inline statement e.g select(All).from(user) for creating a projection using the DEFINE TABLE statement. This is useful for copying data from an existing table in the new table definition. This is similar to making a view in a RDBMS. | A select statement | Y |
as_fn | Same as above as but defined as external function from the struct e.g select_reading_from_user for creating a projection using the DEFINE TABLE statement. This is useful for copying data from an existing table in the new table definition. This is similar to making a view in a RDBMS. | A function name | Y |
permissions | Specify permissions that apply to the table using the for statement. | ForStatement | Y |
permissions_fn | Same as permission but as an external function from the struct. Specify permissions that apply to the table using the for statement. | ForStatement | Y |
define | Generates a DEFINE TABLE statement for the table. This overrides other specific definitions to prevent confusion and collision. You can also invoke an external function directly rather than inlining the function e.g define = "define_student()" | inline code string | Y |
define_fn | Generates a DEFINE TABLE statement for the table. This overrides other specific definitions to prevent confusion and collision. Same as define attribute but expects the function name instead rather than invocation i.e define_student instead of define_student() . You can also invoke an external function directly rather than inlining the function e.g `define = "def |
Field Attributes
Attribute | Description | Type | Optional |
---|---|---|---|
rename | Renames the field. | string | Y |
link_one | Specifies a relationship to a singular record in another node table in the database. | model=NodeEdgeNode, connection ->edge->node | Y |
link_self | Specifies a relationship to a singular record in the same node table in the database. | Node | Y |
link_many | Specifies a relationship to multiple records in another node table in the database. | `Vec<S | |
type | Specify the valid surrealdb field's type. One of any, array, bool, datetime, decimal, duration, float, int, number, object, string, record. | surrealdb field type | Y |
assert | Assert the field's value meets a certain criteria using the an filter using value() function as an operation (e.g value().is_not(NONE) ) or in cond helper function for more complex filter assertion. e.g cond(value().is_not(NONE)).and(value().like("@codebreather")) . | inline code string | Y |
assert_fn | Provide a function to assert the field's value meets a certain criteria. This is similar to assert but is intended for an already created external function which is useful when reusing an assertion e.g is_email . | function name string | Y |
item_type | Only when for nested array. Specifies the type of the items of the array. | Option<FieldTypeWrapper> | Y |
item_assert | Only used for nested array. Asserts a condition on the content. | Option<syn::LitStr> | Y |
item_assert_fn | Only used for nested array. Specifies the function to assert a condition on the content. | Option<syn::Path> | Y |
define | Generates a DEFINE FIELD statement for the table. This overrides other specific definitions to prevent confusion and collision. You can also invoke an external function directly rather than inlining the function e.g define = "define_age()" | inline code string | Y |
define_fn | Generates a DEFINE FIELD statement for the table. This overrides other specific definitions to prevent confusion and collision. Same as define attribute but expects the function name instead rather than invocation i.e define_age instead of define_age() . You can also invoke an external function directly rather than inlining the function e.g `define = "def | ||
skip_serializing | When true, this field will be omitted when serializing the struct. | bool | Y |
Object
In Surreal, an Object is a complex nested data structure that can be embedded
within Nodes, modeled by the Object
trait in Rust. Unlike Nodes, which
represent database tables, Objects do not represent tables on their own.
However, they are crucial in modeling more complex data within a Node. They can
be used directly as a field type or as an element within an array, enabling you
to encapsulate and manage more intricate data structures within your database
models.
Here's an example of a node named Alien that has a nested Rocket object and an array of Rocket objects:
#![allow(unused)] fn main() { use serde::{Deserialize, Serialize}; use surreal_orm::{SurrealSimpleId, Node}; #[derive(Node, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[orm(table = "alien")] pub struct Alien { pub id: SurrealSimpleId<Self>, #[orm(nest_object = "Rocket")] pub favorite_rocket: Rocket, #[orm(nest_array = "Rocket")] pub strong_rockets: Vec<Rocket>, } #[derive(Object, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Rocket { pub name: String, pub strength: u64, } }
Objects in Surreal can be used in two ways: as nested objects (nest_object
)
and as arrays of nested objects (nest_array
). For instance, in an Alien Node,
a Rocket Object can be a single favorite rocket (nest_object
) or a collection
of strong rockets (nest_array
). This powerful feature allows for more complex
nested data to be directly embedded in your models, thus offering a more nuanced
representation of real-world entities in your database.
Notably, the use of nest_object
or nest_array
is validated at compile time.
This ensures that nest_object
is used correctly for the specific Object and
nest_array
corresponds to a vector of that Object, providing a guarantee of
the validity of your data structures before your program runs.
Struct Attributes
Attribute | Description | Type | Optional |
---|---|---|---|
rename_all | Renames all the struct's fields according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y |
Field Attributes
Attribute | Description | Type | Optional |
---|---|---|---|
rename | Renames the field. | string | Y |
Record Ids
The SurrealId
is a wrapper struct that extends the capabilities of
surrealdb::sql::Thing
and provides a more ergonomic interface. It's a static
type representing the id of a model in the Surreal ORM and is a combination of
the model's table name and the id, where the id can be anything that can be
converted into a surrealdb::sql::Id
.
Let's explore how to utilize these ID types both implicitly (through auto-generation via the Default trait) and explicitly (by creating them manually).
- SurrealSimpleId
:
This ID type auto-generates a unique identifier when a new instance of the
struct is created, thanks to the implementation of the Default trait. But you
can also manually generate it using the create_simple_id()
function directly
on the struct.
Example struct:
#![allow(unused)] fn main() { #[derive(Node, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] #[orm(table = "alien")] pub struct Alien { pub id: SurrealSimpleId<Self>, // other fields } }
Creating an instance of Alien with an auto-generated ID (implicit):
#![allow(unused)] fn main() { let alien = Alien { // other fields ..Default::default() }; }
Creating an instance of Alien with a manually generated ID (explicit):
#![allow(unused)] fn main() { let alien = Alien { id: Alien::create_simple_id(), // other fields }; }
- SurrealUuid
:
SurrealUuid<Self>
auto-generates a UUID when a new instance of the struct is
created. You can also manually generate it using the create_uuid()
function on
the struct.
Example struct:
#![allow(unused)] fn main() { #[derive(Node, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] #[orm(table = "account")] pub struct Account { pub id: SurrealUuid<Self>, // other fields } }
Creating an instance of Account with an auto-generated UUID (implicit):
#![allow(unused)] fn main() { let account = Account { // other fields ..Default::default() }; }
Creating an instance of Account with a manually generated UUID (explicit):
#![allow(unused)] fn main() { let account = Account { id: Account::create_uuid(), // other fields }; }
- SurrealUlid
:
SurrealUlid<Self>
auto-generates a ULID when a new instance of the struct is
created. You can also manually generate it using the create_ulid()
function on
the struct.
Example struct:
#![allow(unused)] fn main() { #[derive(Node, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] #[orm(table = "spaceship")] pub struct SpaceShip { pub id: SurrealUlid<Self>, // other fields } }
Creating an instance of SpaceShip with an auto-generated ULID (implicit):
#![allow(unused)] fn main() { let spaceship = SpaceShip { // other fields ..Default::default() }; }
Creating an instance of SpaceShip with a manually generated ULID (explicit):
#![allow(unused)] fn main() { let spaceship = SpaceShip { id: SpaceShip::create_ulid(), // other fields }; }
- SurrealId<Self, T>:
This is the most flexible ID type, allowing for any arbitrary serializable type
T
as the ID. However, it doesn't implement the Default trait, which means you
must manually create instances of this type using the create_id()
function.
Example struct:
#![allow(unused)] fn main() { #[derive(Node, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[orm(table = "weapon")] pub struct Weapon { pub id: SurrealId<Self, String>, // other fields } }
Creating an
instance of Weapon with a manually created ID (explicit):
#![allow(unused)] fn main() { let weapon = Weapon { id: Weapon::create_id("sword".into()), // other fields }; }
These ID types provide various options for users to meet the needs of different scenarios when working with entities in SurrealDB. Whether you want auto-generated identifiers or prefer to create them manually, there's an ID type to suit your requirements.
The SurrealID types in SurrealDB are designed to be flexible and accommodating to various needs for entity identification and linking within the database.
Model Schema
This guide covers the SchemaGetter
trait in SurrealDB, a Rust crate, and
provides examples on how to use it.
The SchemaGetter Trait
This trait is defined as follows:
#![allow(unused)] fn main() { pub trait SchemaGetter { type Schema; fn schema() -> Self::Schema; fn schema_prefixed(prefix: impl Into<ValueLike>) -> Self::Schema; } }
This trait is used for defining schemas for different entities in your database. It contains two associated functions:
schema()
: Returns a schema for an entity. This is used for defining the structure and constraints of the entity.schema_prefixed(prefix: impl Into<ValueLike>)
: This is similar toschema()
, but it allows the schema to be prefixed with a custom value. This can be useful when working with entities that may share similar fields but have different schemas.
The SchemaGetter
trait's primary use is to allow types to be used as a
'Schema' - a representation of the structure of the data you're storing or
retrieving from the database. It's particularly useful in constructing complex
queries with strong type safety.
Examples
The examples below demonstrate the different methods you can utilize in SurrealDB, leveraging the SchemaGetter trait:
Creating and Retrieving Entities:
This piece of code uses the schema()
function of SchemaGetter
to create and
retrieve entities in the database:
#![allow(unused)] fn main() { let _simple_relation = Student::schema() .writes__(Empty) .book(Book::schema().id.equal(Thing::from(("book", "blaze")))) .title; }
This creates a relation between the Student
and Book
entities. It uses the
writes__
method to create a relation indicating the Student
writes a Book
.
The book
call then specifies that the book's id equals a specific Thing
entity.
Pattern Selection:
SurrealDB also allows the pattern-like selection of entities:
#![allow(unused)] fn main() { let student_id = Student::create_id("oyelowo"); let book_id = Book::create_id("2"); let likes = StudentLiksBook::table(); let writes = StudentWritesBook::table(); let writes::Schema { timeWritten, .. } = StudentWritesBook::schema(); let aliased_connection = Student::with(student_id) .writes__(Empty) .writes__(Empty) .writes__(any_other_edges(&[writes, likes]).where_(timeWritten.less_than_or_equal(50))) .book(book_id) .__as__(Student::aliases().writtenBooks); }
In this case, we are selecting all the books that a specific student wrote where
the timeWritten
is less than or equal to 50. This query is an example of how
you can combine different methods and concepts provided by SurrealDB to form
complex, yet understandable, queries.
Modifying and Updating Entities:
The following example illustrates how to modify and update entities:
#![allow(unused)] fn main() { let ref id = created_weapon.clone().id; let weapon::Schema { strength, .. } = Weapon::schema(); update::<Weapon>(id) .set(strength.increment_by(5u64)) .run(db.clone()) .await?; let updated = update::<Weapon>(id) .set(strength.decrement_by(2u64)) .return_one(db.clone()) .await?; let selected: Option<Weapon> = select(All) .from(Weapon::table()) .return_one(db.clone()) .await?; assert_eq!(updated.unwrap().strength, 8); assert_eq!(selected.unwrap().strength, 8); }
Links, Nestings and Relations
link_one
: It is an attribute used to define a one-to-one relationship between two Nodes. For example, consider theAlien
struct with the fieldweapon
:
#![allow(unused)] fn main() { use surreal_orm::{Serialize, Deserialize,LinkOne, SurrealSimpleId, Node}; #[derive(Node, Serialize, Deserialize, Debug)] #[orm(table = "alien")] pub struct Alien { pub id: SurrealSimpleId<Self>, // #[orm(link_one = "Weapon", type_ = "record(weapon)")] #[orm(link_one = "Weapon")] pub best_weapon: LinkOne<Weapon>, } #[derive(Node, Serialize, Deserialize, Debug)] #[orm(table = "weapon")] pub struct Weapon { pub id: SurrealSimpleId<Self>, } }
This attribute indicates that an Alien
can have a single best Weapon
. The
relationship is represented by a foreign key in the database table, and the
type
attribute specifies the database type for the relationship.
link_many
: It is an attribute used to define a one-to-many relationship between two Nodes. For instance, in theAlien
struct, we have thespace_ships
field:
#![allow(unused)] fn main() { use surreal_orm::{Serialize, Deserialize, LinkMany, SurrealSimpleId, Node}; #[derive(Node, Serialize, Deserialize, Debug)] #[orm(table = "alien")] pub struct Alien { pub id: SurrealSimpleId<Self>, // #[orm(link_many = "SpaceShip", type_ = "array", item_type = "record(space_ship)")] #[orm(link_many = "SpaceShip")] pub space_ships: LinkMany<SpaceShip>, } #[derive(Node, Serialize, Deserialize, Debug)] #[orm(table = "space_ship")] pub struct SpaceShip { pub id: SurrealSimpleId<Self>, } }
This attribute indicates that an Alien
can have multiple SpaceShip
instances
associated with it. The relationship is represented by a foreign key or a join
table in the database, and the type
attribute specifies the database type for
the relationship.
nest_object
: It is an attribute used to embed a single Object within a Node. In theAlien
struct, we have theweapon
field:
#![allow(unused)] fn main() { use surreal_orm::{Serialize, Deserialize,SurrealSimpleId, Node, Object}; #[derive(Node, Serialize, Deserialize, Debug)] #[orm(table = "alien")] pub struct Alien { pub id: SurrealSimpleId<Self>, #[orm(nest_object = "Rocket")] pub favorite_rocket: Rocket, } #[derive(Object, Serialize, Deserialize, Debug)] #[orm(table = "rocket")] pub struct Rocket { } }
This attribute specifies that an Alien2
has a nested Rocket
object
representing its weapon. The Rocket
object is stored as part of the Alien2
Node in the database.
nest_array
: It is an attribute used to embed multiple Objects within a Node. Although not explicitly used in the provided code examples, it would be similar toNestObject
, but with a collection type field (e.g.,Vec<Rocket>
).
#![allow(unused)] fn main() { use surreal_orm::{Serialize, Deserialize,SurrealSimpleId, Node, Object}; #[derive(Node, Serialize, Deserialize, Debug)] #[orm(table = "alien")] pub struct Alien { pub id: SurrealSimpleId<Self>, #[orm(nest_array = "Rocket")] pub big_rockets: Vec<Rocket>, } #[derive(Node, Serialize, Deserialize, Debug)] #[orm(table = "rocket")] pub struct Rocket { } }
relate
: It is an attribute used to define a read-only relationship between two Nodes. In theAlien
struct, we have theplanets_to_visit
field:
#![allow(unused)] fn main() { use surreal_orm::{Serialize, Deserialize, SurrealSimpleId, Node, Edge, Relate}; #[derive(Node, Serialize, Deserialize, Debug)] #[orm(table = "alien")] pub struct Alien { pub id: SurrealSimpleId<Self>, #[orm(relate(model = "AlienVisitsPlanet", connection = "->visits->planet"))] #[serde(skip_serializing, default)] pub planets_to_visit: Relate<Planet>, } #[derive(Node, Serialize, Deserialize, Debug, Clone, Default)] #[serde(rename_all = "camelCase")] #[orm(table = "planet")] pub struct Planet { pub id: SurrealSimpleId<Self>, pub population: u64, } // Visits #[derive(Edge, Serialize, Deserialize, Debug, Clone, Default)] #[serde(rename_all = "camelCase")] #[orm(table = "visits")] pub struct Visits<In: Node, Out: Node> { pub id: SurrealSimpleId<Self>, #[serde(rename = "in")] pub in_: LinkOne<In>, pub out: LinkOne<Out>, pub time_visited: Duration, } // Connects Alien to Planet via Visits pub type AlienVisitsPlanet = Visits<Alien, Planet>; }
This attribute specifies that an Alien
has a read-only relationship with
Planet
through the AlienVisitsPlanet
model. The connection
attribute
describes the relationship path between the Nodes. The relationship is read-only
because the serde(skip_serializing)
attribute is used to prevent it from being
serialized.
These attributes provide additional information to Surreal for modeling relationships and embedding Objects within Nodes, allowing for more complex and flexible database schema designs.
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
Loaders
Loaders in the Surreal ORM are functions that fetch different kinds of related
records (links) from the database. These loaders provide different ways of
handling these related records, based on their type and their existence in the
database. Here, we discuss some of the "load" types that are part of the
ReturnableStandard
trait.
load_links
The load_links
function sets the return type to projections and fetches all
record links. It defaults values to null for referenced records that do not
exist.
For instance, if you have a User
model that has a Posts
link (i.e., each
User can have multiple Posts), you can use load_links
to fetch all the Posts
linked to a User
. If a Post
does not exist, the function defaults its value
to null.
#![allow(unused)] fn main() { let user = User::find(1).load_links(vec!["posts"]).unwrap(); }
#![allow(unused)] fn main() { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); #[derive(Node, Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] #[orm(table = "alien")] pub struct Alien { pub id: SurrealSimpleId<Self>, #[orm(link_self = "Alien")] pub ally: LinkSelf<Alien>, #[orm(link_one = "Weapon")] pub weapon: LinkOne<Weapon>, // Again, we dont have to provide the type attribute, it can auto detect #[orm(link_many = "SpaceShip")] pub space_ships: LinkMany<SpaceShip>, // This is a read only field #[orm(relate(model = "AlienVisitsPlanet", connection = "->visits->planet"))] #[serde(skip_serializing, default)] pub planets_to_visit: Relate<Planet>, } #[derive(Node, Serialize, Deserialize, Debug, Clone, Default)] #[serde(rename_all = "camelCase")] #[orm(table = "weapon")] pub struct Weapon { pub id: SurrealSimpleId<Self>, pub name: String, // pub strength: u64, #[orm(type_ = "int")] pub strength: Strength, pub created: DateTime<Utc>, #[orm(nest_object = "Rocket")] pub rocket: Rocket, } type Strength = u64; #[derive(Node, Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] #[orm(table = "space_ship")] pub struct SpaceShip { pub id: SurrealId<Self, String>, pub name: String, pub created: DateTime<Utc>, } let weapon = || Weapon { name: "Laser".to_string(), created: Utc::now(), ..Default::default() }; let weapon1 = weapon(); let weapon2 = weapon(); let space_ship = SpaceShip { id: SpaceShip::create_id("gbanda".into()), name: "SpaceShip1".to_string(), created: Utc::now(), }; let space_ship2 = SpaceShip { id: SpaceShip::create_id("halifax".into()), name: "SpaceShip2".to_string(), created: Utc::now(), }; let space_ship3 = SpaceShip { id: SpaceShip::create_id("alberta".into()), name: "Oyelowo".to_string(), created: Utc::now(), }; assert_eq!(weapon1.clone().id.to_thing().tb, "weapon"); // create first record to weapon table let created_weapon = create() .content(weapon1.clone()) .get_one(db.clone()) .await?; assert_eq!(created_weapon.id.to_thing(), weapon1.id.to_thing()); let select1: Vec<Weapon> = select(All) .from(Weapon::table()) .return_many(db.clone()) .await?; // weapon table should have one record assert_eq!(select1.len(), 1); // Create second record let created_weapon = create() .content(weapon2.clone()) .return_one(db.clone()) .await?; let select2: Vec<Weapon> = select(All) .from(Weapon::table()) .return_many(db.clone()) .await?; // weapon table should have two records after second creation assert_eq!(select2.len(), 2); let created_spaceship1 = create() .content(space_ship.clone()) .get_one(db.clone()) .await?; let created_spaceship2 = create() .content(space_ship2.clone()) .get_one(db.clone()) .await?; let created_spaceship3 = create() .content(space_ship3.clone()) .get_one(db.clone()) .await?; let point = point! { x: 40.02f64, y: 116.34, }; let territory = line_string![(x: 40.02, y: 116.34), (x: 40.02, y: 116.35), (x: 40.03, y: 116.35), (x: 40.03, y: 116.34), (x: 40.02, y: 116.34)]; let polygon = polygon![(x: 40.02, y: 116.34), (x: 40.02, y: 116.35), (x: 40.03, y: 116.35), (x: 40.03, y: 116.34), (x: 40.02, y: 116.34)]; let unsaved_alien = Alien { id: Alien::create_simple_id(), ally: LinkSelf::null(), weapon: LinkOne::from(created_weapon.unwrap()), space_ships: LinkMany::from(vec![ created_spaceship1.clone(), created_spaceship2.clone(), created_spaceship3.clone(), ]), planets_to_visit: Relate::null(), }; assert!(unsaved_alien.weapon.get_id().is_some()); assert!(unsaved_alien.weapon.value().is_none()); // Check fields value fetching let alien::Schema { weapon, .. } = Alien::schema(); let created_alien = create() .content(unsaved_alien.clone()) .load_links(vec![weapon])? .get_one(db.clone()) .await?; let ref created_alien = created_alien.clone(); // id is none because ally field is not created. assert!(created_alien.ally.get_id().is_none()); // .value() is None because ally is not created. assert!(created_alien.ally.value().is_none()); // Weapon is created at weapon field and also loaded. // get_id is None because weapon is loaded. assert!(created_alien.weapon.get_id().is_none()); // .value() is Some because weapon is loaded. assert!(created_alien.weapon.value().is_some()); // Spaceships created at weapon field and also loaded. assert_eq!(created_alien.space_ships.is_empty(), false); assert_eq!(created_alien.space_ships.len(), 3); assert_eq!( created_alien .space_ships .iter() .map(|x| x.get_id().unwrap().to_string()) .collect::<Vec<_>>(), vec![ created_spaceship1.id.to_string(), created_spaceship2.id.to_string(), created_spaceship3.id.to_string(), ] ); let created_alien_with_fetched_links = create() .content(unsaved_alien.clone()) .load_link_manys()? .return_one(db.clone()) .await?; let ref created_alien_with_fetched_links = created_alien_with_fetched_links.unwrap(); let alien_spaceships = created_alien_with_fetched_links.space_ships.values(); assert_eq!(created_alien_with_fetched_links.space_ships.keys().len(), 3); assert_eq!( created_alien_with_fetched_links .space_ships .keys_truthy() .len(), 0 ); }
load_all_links
The load_all_links
function sets the return type to projections and fetches
all record link values. For link_one
and link_self
types, it returns null if
the link is null or if the reference does not exist. For link_many
type, it
returns None
for items that are null or the references that do not exist.
Assume you have a User
model with link_one
type Profile
, link_self
type
Friends
, and link_many
type Posts
. You can use load_all_links
to fetch
all these linked records.
#![allow(unused)] fn main() { let user = User::find(1).load_all_links().unwrap(); }
load_link_manys
The load_link_manys
function sets the return type to projections and fetches
all record link values for link_many
fields, including the null record links.
So, if a User
has multiple Posts
, this function fetches all Posts
including the ones that are null.
#![allow(unused)] fn main() { let user = User::find(1).load_link_manys().unwrap(); }
load_link_ones
The load_link_ones
function sets the return type to projections and fetches
all record link values for link_one
fields. It defaults to null if the
reference does not exist.
#![allow(unused)] fn main() { let user = User::find(1).load_link_ones().unwrap(); }
load_link_selfs
The load_line_selfs
function sets the return type to projections and fetches
all record link values for link_self
fields. It defaults to null if the
reference does not exist.
#![allow(unused)] fn main() { let user = User::find(1).load_line_selfs().unwrap(); }
In conclusion, loaders provide a flexible way to handle linked records in your database. Whether you want to fetch all links, fetch links of a specific type, or handle null references in a certain way, loaders have got you covered. They are a powerful tool in the Surreal ORM, simplifying complex database operations.
Return Types
Return types in the Surreal ORM define how database operations result in
returned data. They allow the specification of the format and structure of the
data returned after running a database operation. In this chapter, we will
discuss some of the "return" types that are part of the ReturnableStandard
trait.
return_one
The return_one
function runs a statement against the database and returns a
single result. If the result contains more than one record, it throws an error.
Consider a scenario where you want to fetch a single user from your database.
You can use return_one
to get the User
record:
#![allow(unused)] fn main() { let user = User::find(1).return_one(db).await.unwrap(); }
return_many
The return_many
function runs a statement against the database and returns
multiple results.
For instance, if you want to fetch all users from your database, you can use
return_many
to get the User
records:
#![allow(unused)] fn main() { let users = User::all().return_many(db).await.unwrap(); }
return_none
The return_none
function runs a statement against the database and returns no
result.
This is particularly useful when you perform operations that don't require a return value. For example, deleting a user:
#![allow(unused)] fn main() { User::delete(1).return_none(db).await.unwrap(); }
return_first
The return_first
function runs a statement against the database and returns
the first result.
For example, to get the first user in the database:
#![allow(unused)] fn main() { let user = User::all().return_first(db).await.unwrap(); }
return_many_before
The return_many_before
function runs a statement against the database and
returns the many results before the change.
This is useful when you want to compare the state of the records before and after a database operation. For instance, updating a user's profile:
#![allow(unused)] fn main() { let users_before_update = User::all().return_many_before(db).await.unwrap(); User::update(1, new_profile_data).return_none(db).await.unwrap(); }
In conclusion, return types provide a way to control the data returned by database operations. Whether you want a single record, multiple records, the first record, or no record at all, return types allow you to specify the outcome.
Helper Methods in surreal_orm
The surreal_orm
library offers a set of utility functions encapsulated in the
SurrealCrud
and SurrealCrudNode
traits. These methods provide a high-level
abstraction over raw database statements, simplifying CRUD operations.
Preparations
Before we dive into the helper methods, let's set up our environment:
#![allow(unused)] fn main() { use surreal_models::{space_ship, weapon, SpaceShip, Weapon}; use surreal_orm::{ statements::{insert, select, select_value}, *, }; use surrealdb::{ engine::local::{Db, Mem}, Surreal, }; async fn create_test_data(db: Surreal<Db>) { let space_ships = (0..1000) .map(|i| Weapon { name: format!("weapon-{}", i), strength: i, ..Default::default() }) .collect::<Vec<Weapon>>(); insert(space_ships).run(db.clone()).await.unwrap(); } }
1. save
Method
The save
method can either create a new record or update an existing one in
the database. You can think of it as an upsert method.
#![allow(unused)] fn main() { #[tokio::test] async fn test_save() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let ss_id = SpaceShip::create_id("num-1".into()); let spaceship = SpaceShip { id: ss_id.clone(), name: "spaceship-1".into(), created: chrono::Utc::now(), }; let spaceship = spaceship.save().get_one(db.clone()).await?; let saved_spaceship = SpaceShip::find_by_id(ss_id.clone()) .get_one(db.clone()) .await?; assert_eq!(spaceship.id.to_thing(), saved_spaceship.id.to_thing()); assert_eq!(spaceship.name, saved_spaceship.name); Ok(()) } }
2. find_by_id
Method
Retrieve a record by its ID:
#![allow(unused)] fn main() { #[tokio::test] async fn test_find_by_id() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let spaceship = SpaceShip { id: SpaceShip::create_id("num-1".into()), name: "spaceship-1".into(), created: chrono::Utc::now(), }; spaceship.clone().save().run(db.clone()).await?; let found_spaceship = SpaceShip::find_by_id(spaceship.id.clone()) .get_one(db.clone()) .await?; assert_eq!(spaceship.id.to_thing(), found_spaceship.id.to_thing()); Ok(()) } }
3. find_where
Method
Retrieve records based on specific conditions:
#![allow(unused)] fn main() { #[tokio::test] async fn test_find_where() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let spaceship = SpaceShip { id: SpaceShip::create_id("num-1".into()), name: "spaceship-1".into(), created: chrono::Utc::now(), }; let _spaceship2 = SpaceShip { id: SpaceShip::create_id("num-2".into()), name: "spaceship-2".into(), created: chrono::Utc::now(), } .save() .run(db.clone()) .await?; let _spaceschip = spaceship.clone().save().get_one(db.clone()).await?; let space_ship::Schema { name, id, .. } = SpaceShip::schema(); let found_spaceships = SpaceShip::find_where(id.is_not(NULL)) .return_many(db.clone()) .await?; assert_eq!(found_spaceships.len(), 2); let found_spaceships = SpaceShip::find_where(name.equal("spaceship-1")) .return_many(db.clone()) .await?; assert_eq!(found_spaceships.len(), 1); assert_eq!(found_spaceships[0].id.to_thing(), spaceship.id.to_thing()); let found_spaceship = SpaceShip::find_where(name.equal("spaceship-1")) .get_one(db.clone()) .await?; assert_eq!(found_spaceship.id.to_thing(), spaceship.id.to_thing()); Ok(()) } }
4. count_where
Method
Count records based on specific conditions:
#![allow(unused)] fn main() { #[tokio::test] async fn test_count_where() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); create_test_data(db.clone()).await; let weapon::Schema { strength, .. } = &Weapon::schema(); let weapons_query = Weapon::count_where(strength.gte(500)); let weapons_count = weapons_query.get(db.clone()).await?; assert_eq!( weapons_query.to_raw().build(), "SELECT VALUE count FROM (SELECT count(strength >= 500) FROM weapon GROUP ALL);" ); assert_eq!(weapons_count, 500); Ok(()) } }
5. count_all
Method
Count all records:
#![allow(unused)] fn main() { #[tokio::test] async fn test_count_all() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); create_test_data(db.clone()).await; let weapons_query = Weapon::count_all(); let weapons_count = weapons_query.get(db.clone()).await?; assert_eq!( weapons_query.to_raw().build(), "SELECT VALUE count FROM (SELECT count() FROM weapon GROUP ALL);" ); assert_eq!(weapons_count, 1000); Ok(()) } }
6. delete
Method
This method deletes the current record instance from the database.
#![allow(unused)] fn main() { #[tokio::test] async fn test_delete() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let spaceship = SpaceShip { id: SpaceShip::create_id("num-1".into()), name: "spaceship-1".into(), created: chrono::Utc::now(), }; let spaceship = spaceship.save().get_one(db.clone()).await?; let found_spaceship = SpaceShip::find_by_id(spaceship.id.clone()) .return_many(db.clone()) .await?; assert_eq!(found_spaceship.len(), 1); spaceship.clone().delete().run(db.clone()).await?; let found_spaceship = SpaceShip::find_by_id(spaceship.id.clone()) .return_many(db.clone()) .await?; assert!(found_spaceship.is_empty()); assert_eq!(found_spaceship.len(), 0); Ok(()) } }
7. delete_by_id
Method
This method deletes a record by its ID.
#![allow(unused)] fn main() { #[tokio::test] async fn test_delete_by_id() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let spaceship = SpaceShip { id: SpaceShip::create_id("num-1".into()), name: "spaceship-1".into(), created: chrono::Utc::now(), }; let spaceship = spaceship.save().get_one(db.clone()).await?; let found_spaceships = SpaceShip::find_by_id(spaceship.id.clone()) .return_many(db.clone()) .await?; assert_eq!(found_spaceships.len(), 1); SpaceShip::delete_by_id(spaceship.id.clone()) .run(db.clone()) .await?; let found_spaceships = SpaceShip::find_by_id(spaceship.id.clone()) .return_many(db.clone()) .await?; assert_eq!(found_spaceships.len(), 0); Ok(()) } }
8. delete_where
Method
This method deletes records based on a specific condition.
#![allow(unused)] fn main() { #[tokio::test] async fn test_delete_where() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let spaceship = SpaceShip { id: SpaceShip::create_id("num-1".into()), name: "spaceship-1".into(), created: chrono::Utc::now(), }; spaceship.save().run(db.clone()).await.unwrap(); let space_ship::Schema { name, .. } = SpaceShip::schema(); let found_spaceships = SpaceShip::find_where(name.like("spaceship-1")) .return_many(db.clone()) .await?; assert_eq!(found_spaceships.len(), 1); SpaceShip::delete_where(name.like("spaceship")) .run(db.clone()) .await?; let found_spaceships = SpaceShip::find_where(name.like("spaceship-1")) .return_many(db.clone()) .await?; assert_eq!(found_spaceships.len(), 0); Ok(()) } }
9. create
Method
This method creates a new record in the database. It's specifically for nodes.
#![allow(unused)] fn main() { #[tokio::test] async fn test_create() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let ss_id = SpaceShip::create_id(format!("num-{}", 1)); let spaceship = SpaceShip { id: ss_id.clone(), name: format!("spaceship-{}", 1), created: chrono::Utc::now(), }; let spaceship = spaceship.create().get_one(db.clone()).await?; // Second attempt should fail since it will be duplicate. spaceship .clone() .create() .get_one(db.clone()) .await .expect_err("should fail"); let saved_spaceship = SpaceShip::find_by_id(ss_id.clone()) .get_one(db.clone()) .await?; assert_eq!(spaceship.id.to_thing(), saved_spaceship.id.to_thing()); assert_eq!(spaceship.name, saved_spaceship.name); Ok(()) } }
This wraps up the explanations and demonstrations for all the helper methods in surreal_orm.
Introduction to Utility Functions
In surreal_orm, utility functions are designed to simplify complex database operations by abstracting them into easy-to-use methods. They help streamline CRUD operations, reducing the need for verbose database statements.
Read Methods
Read methods allow for the retrieval of data from the database. They cater to various use cases, from fetching a single record using its unique identifier to obtaining multiple records based on specific conditions.
find_by_id
2. find_by_id
Method
The find_by_id method provides a straightforward way to fetch a record from the database using its unique ID.
#![allow(unused)] fn main() { #[tokio::test] async fn test_find_by_id() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let spaceship = SpaceShip { id: SpaceShip::create_id("num-1".into()), name: "spaceship-1".into(), created: chrono::Utc::now(), }; spaceship.clone().save().run(db.clone()).await?; let found_spaceship = SpaceShip::find_by_id(spaceship.id.clone()) .get_one(db.clone()) .await?; assert_eq!(spaceship.id.to_thing(), found_spaceship.id.to_thing()); Ok(()) } }
find_where
3. find_where
Method
For more complex data retrieval needs, the find_where method allows you to specify conditions to determine which records to fetch.
#![allow(unused)] fn main() { #[tokio::test] async fn test_find_where() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let spaceship = SpaceShip { id: SpaceShip::create_id("num-1".into()), name: "spaceship-1".into(), created: chrono::Utc::now(), }; let _spaceship2 = SpaceShip { id: SpaceShip::create_id("num-2".into()), name: "spaceship-2".into(), created: chrono::Utc::now(), } .save() .run(db.clone()) .await?; let _spaceschip = spaceship.clone().save().get_one(db.clone()).await?; let space_ship::Schema { name, id, .. } = SpaceShip::schema(); let found_spaceships = SpaceShip::find_where(id.is_not(NULL)) .return_many(db.clone()) .await?; assert_eq!(found_spaceships.len(), 2); let found_spaceships = SpaceShip::find_where(name.equal("spaceship-1")) .return_many(db.clone()) .await?; assert_eq!(found_spaceships.len(), 1); assert_eq!(found_spaceships[0].id.to_thing(), spaceship.id.to_thing()); let found_spaceship = SpaceShip::find_where(name.equal("spaceship-1")) .get_one(db.clone()) .await?; assert_eq!(found_spaceship.id.to_thing(), spaceship.id.to_thing()); Ok(()) } }
Create, Update Methods
Creating and updating records are fundamental operations in any database. surreal_orm provides methods to easily handle both these tasks.
save
1. save
Method
The save method is versatile—it can either create a new record or update an existing one, making it an "upsert" function.
#![allow(unused)] fn main() { #[tokio::test] async fn test_save() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let ss_id = SpaceShip::create_id("num-1".into()); let spaceship = SpaceShip { id: ss_id.clone(), name: "spaceship-1".into(), created: chrono::Utc::now(), }; let spaceship = spaceship.save().get_one(db.clone()).await?; let saved_spaceship = SpaceShip::find_by_id(ss_id.clone()) .get_one(db.clone()) .await?; assert_eq!(spaceship.id.to_thing(), saved_spaceship.id.to_thing()); assert_eq!(spaceship.name, saved_spaceship.name); Ok(()) } }
create
9. create
Method
While the save method is versatile, the create method is specialized for
creating new records. It's specifically for nodes. Unlike the save
method,
rather than updating the existing record by its id
, it throws and error when a
record already exists.
#![allow(unused)] fn main() { #[tokio::test] async fn test_create() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let ss_id = SpaceShip::create_id(format!("num-{}", 1)); let spaceship = SpaceShip { id: ss_id.clone(), name: format!("spaceship-{}", 1), created: chrono::Utc::now(), }; let spaceship = spaceship.create().get_one(db.clone()).await?; // Second attempt should fail since it will be duplicate. spaceship .clone() .create() .get_one(db.clone()) .await .expect_err("should fail"); let saved_spaceship = SpaceShip::find_by_id(ss_id.clone()) .get_one(db.clone()) .await?; assert_eq!(spaceship.id.to_thing(), saved_spaceship.id.to_thing()); assert_eq!(spaceship.name, saved_spaceship.name); Ok(()) } }
Delete Methods
Deletion is another crucial CRUD operation, and surreal_orm offers methods to delete records in various ways.
delete
This method facilitates the deletion of a specific record instance
#![allow(unused)] fn main() { #[tokio::test] async fn test_delete() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let spaceship = SpaceShip { id: SpaceShip::create_id("num-1".into()), name: "spaceship-1".into(), created: chrono::Utc::now(), }; let spaceship = spaceship.save().get_one(db.clone()).await?; let found_spaceship = SpaceShip::find_by_id(spaceship.id.clone()) .return_many(db.clone()) .await?; assert_eq!(found_spaceship.len(), 1); spaceship.clone().delete().run(db.clone()).await?; let found_spaceship = SpaceShip::find_by_id(spaceship.id.clone()) .return_many(db.clone()) .await?; assert!(found_spaceship.is_empty()); assert_eq!(found_spaceship.len(), 0); Ok(()) } }
delete_by_id
7. delete_by_id
Method
If you know the ID of a record you wish to delete, the delete_by_id method makes the task straightforward.
#![allow(unused)] fn main() { #[tokio::test] async fn test_delete_by_id() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let spaceship = SpaceShip { id: SpaceShip::create_id("num-1".into()), name: "spaceship-1".into(), created: chrono::Utc::now(), }; let spaceship = spaceship.save().get_one(db.clone()).await?; let found_spaceships = SpaceShip::find_by_id(spaceship.id.clone()) .return_many(db.clone()) .await?; assert_eq!(found_spaceships.len(), 1); SpaceShip::delete_by_id(spaceship.id.clone()) .run(db.clone()) .await?; let found_spaceships = SpaceShip::find_by_id(spaceship.id.clone()) .return_many(db.clone()) .await?; assert_eq!(found_spaceships.len(), 0); Ok(()) } }
delete_where
8. delete_where
Method
For scenarios where you need to delete multiple records based on a condition, the delete_where method comes in handy.
#![allow(unused)] fn main() { #[tokio::test] async fn test_delete_where() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let spaceship = SpaceShip { id: SpaceShip::create_id("num-1".into()), name: "spaceship-1".into(), created: chrono::Utc::now(), }; spaceship.save().run(db.clone()).await.unwrap(); let space_ship::Schema { name, .. } = SpaceShip::schema(); let found_spaceships = SpaceShip::find_where(name.like("spaceship-1")) .return_many(db.clone()) .await?; assert_eq!(found_spaceships.len(), 1); SpaceShip::delete_where(name.like("spaceship")) .run(db.clone()) .await?; let found_spaceships = SpaceShip::find_where(name.like("spaceship-1")) .return_many(db.clone()) .await?; assert_eq!(found_spaceships.len(), 0); Ok(()) } }
Count and Aggregation Methods
Counting records and performing aggregate operations are common tasks when working with databases. surreal_orm provides methods to make these tasks efficient and easy.
count_where
4. count_where
Method
To count records based on a condition, you can use the count_where method.
#![allow(unused)] fn main() { #[tokio::test] async fn test_count_where() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); create_test_data(db.clone()).await; let weapon::Schema { strength, .. } = &Weapon::schema(); let weapons_query = Weapon::count_where(strength.gte(500)); let weapons_count = weapons_query.get(db.clone()).await?; assert_eq!( weapons_query.to_raw().build(), "SELECT VALUE count FROM (SELECT count(strength >= 500) FROM weapon GROUP ALL);" ); assert_eq!(weapons_count, 500); Ok(()) } }
count_all
5. count_all
Method
When you need a count of all records in a table, the count_all method is your go-to.
#![allow(unused)] fn main() { #[tokio::test] async fn test_count_all() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); create_test_data(db.clone()).await; let weapons_query = Weapon::count_all(); let weapons_count = weapons_query.get(db.clone()).await?; assert_eq!( weapons_query.to_raw().build(), "SELECT VALUE count FROM (SELECT count() FROM weapon GROUP ALL);" ); assert_eq!(weapons_count, 1000); Ok(()) } }
Statements
Use Statement
The use
statement in Surreal ORM is used to switch the active namespace and
database. This documentation provides an overview of the use
statement and its
usage.
Table of Contents
Introduction
The use
statement in Surreal ORM allows you to switch the active namespace and
database. By specifying the desired namespace and/or database, you can focus
your queries and operations on specific areas of your database.
Syntax
The basic syntax of the use
statement is as follows:
#![allow(unused)] fn main() { use_() .namespace(namespace) .database(database); }
The use
statement supports the following methods:
.namespace(namespace)
: Specifies the namespace to use..database(database)
: Specifies the database to use..build()
: Builds theuse
statement.
Examples
Using the use
Statement with Namespace
To switch the active namespace using the use
statement, you can use the
following code:
#![allow(unused)] fn main() { use surreal_orm::statements::use_; use surreal_orm::models::Namespace; let use_statement = use_() .namespace(Namespace::from("mars".to_string())); assert_eq!(use_statement, "USE NS mars;"); }
In the above example, the use
statement is used to switch the active namespace
to "mars". The resulting use statement is "USE NS mars;".
Using the use
Statement with Database
To switch the active database using the use
statement, you can use the
following code:
#![allow(unused)] fn main() { use surreal_orm::statements::use_; use surreal_orm::models::Database; let use_statement = use_() .database(Database::from("root".to_string())); assert_eq!(use_statement, "USE DB root;"); }
In the above example, the use
statement is used to switch the active database
to "root". The resulting use statement is "USE DB root;".
Using the use
Statement with Namespace and Database
You can also switch both the active namespace and database using the use
statement. Here's an example:
#![allow(unused)] fn main() { use surreal_orm::statements::use_; use surreal_orm::models::{Namespace, Database}; let use_statement = use_() .namespace(Namespace::from("mars".to_string())) .database(Database::from("root".to_string())); assert_eq!(use_statement, "USE DB root NS mars;"); }
In the above example, the use
statement is used to switch the active namespace
to "mars" and the active database to "root". The resulting use statement is "USE
DB root NS mars;".
You have now learned how to use the use
statement in Surreal ORM to switch the
active namespace and database. This allows you to focus your queries and
operations on specific areas of your database.
Let Statement
The let
statement in Surreal ORM allows you to bind variables within a code
block. It simplifies complex queries and enables parameter handling.
Table of Contents
Recommended Approach
In the recommended approach, you can use the let
statement within the block!
macro. This approach provides a natural syntax that handles variable bindings
and parameter references automatically.
Using let
or LET
Statement within block!
Macro
To define variables and bind them within a code block, you can use the let
statement (or LET
statement) within the block!
macro. This approach offers
simplicity and automation in handling variable bindings and parameter
references. Let's take a look at an example:
#![allow(unused)] fn main() { let alien = Table::new("alien"); let metrics = Table::new("metrics"); let strength = Field::new("strength"); let code_block = block! { let strengths = select_value(strength).from(alien); let total = math::sum!(strengths); let count = count!(strengths); let name = "Oyelowo"; }; // This is equivalent to the above. Note: This is not to be confused with actual Rust's native `let` keyword. let code_block = block! { LET strengths = select_value(strength).from(alien); LET total = math::sum!(strengths); LET count = count!(strengths); LET name = "Oyelowo"; }; }
In the code snippet above, the let
(or LET
) statements bind the variables
strengths
, total
, count
, and name
within the code block. These variables
are automatically handled by the ORM, simplifying the query construction
process.
The generated SQL query for this code block would look like:
LET $strengths = (SELECT VALUE strength FROM alien);
LET $total = math::sum($strengths);
LET $count = count($strengths);
LET $name = 'Oyelowo';
The recommended approach using the let
statement (or LET
statement) within
the block!
macro is preferred because it provides a clean and concise syntax,
handles variable bindings and parameter referencing automatically, and promotes
code readability.
Less Recommended Approach
The less recommended approach involves using the let_!
macro to bind variables
manually within a code block. Although it provides flexibility, it requires more
manual handling of parameters and can be error-prone.
Using let_!
Macro
Here's an example of using the let_!
macro to define variables within a code
block:
#![allow(unused)] fn main() { let_!(strengths = select_value(strength).from(alien)); let_!(total = math::sum!(strengths)); let_!(count = count!(strengths)); let_!(name = "Oyelowo"); chain(strengths).chain(total).chain(count).chain(name) }
In the code snippet above, the let_!
macro is used to bind
variablesstrengths
, total
, count
, and name
within the code block. The
variables are manually defined and then chained together using thechain
function.
The generated SQL query for this code block would look like:
LET $strengths = (SELECT VALUE strength FROM alien);
LET $total = math::sum($strengths);
LET $count = count($strengths);
LET $name = 'Oyelowo';
The less recommended approach using the let_!
macro requires explicit
definition and chaining of variables, making the code more complex and
error-prone compared to the recommended approach.
Least Recommended Approach
The least recommended approach involves using the let
statements with the
let_
function to bind variables manually within a code block. This approach
requires even more manual handling of parameters and is prone to errors.
Using let
Statements with let_
Function
Here's another example of using the let
statements with the let_
function to
bind variables within a code block:
#![allow(unused)] fn main() { let strengths = let_("strengths").equal_to(select_value(strength).from(alien)); let total = let_("total").equal_to(math::sum!(strengths)); let count = let_("count").equal_to(count!(strengths)); let name = let_("name").equal_to("Oyelowo"); chain(strengths).chain(total).chain(count).chain(name); }
In this example, the let_
function is used to define variables strengths
,
total
, count
, and name
within the code block. The variables are manually
defined and then chained together using the chain
function.
The generated SQL query for this code block would look like:
LET $strengths = (SELECT VALUE strength FROM alien);
LET $total = math::sum($strengths);
LET $count = count($strengths);
LET $name = 'Oyelowo';
Similar to the previous approach, the use of let
statements with the let_
function in the least recommended approach requires explicit variable definition
and chaining, making the code more complex and error-prone.
It is generally recommended to use the recommended approach with the let
statement (or LET
statement) within the block!
macro for better readability,
automation of variable bindings, and parameter handling.
That concludes the documentation for the let
statement in Surreal ORM. Use the
recommended approach to simplify complex queries and handle variable bindings
effortlessly.
Begin
Cancel Statement
The cancel
statement in Surreal ORM is used to cancel and rollback a
transaction, discarding any changes made within the transaction. It ensures that
the database remains unaffected by the transaction.
Table of Contents
Recommended Approaches
Using block!
Macro with Cancel Statement also Within Block for Chaining Multiple Statements
To perform a transaction and cancel it, discarding any changes made within the
transaction, you can use the block!
macro to chain multiple statements
together. The cancel_transaction
statement is used within the block!
macro
to explicitly indicate the cancellation of the transaction. Let's take a look at
an example:
#![allow(unused)] fn main() { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let ref id1 = Account::create_id("one".into()); let ref id2 = Account::create_id("two".into()); let acc = Account::schema(); let amount_to_transfer = 300.00; block! { BEGIN TRANSACTION; LET acc1 = create().content(Account { id: id1.clone(), balance: 135_605.16, }); LET acc2 = create().content(Account { id: id2.clone(), balance: 91_031.31, }); LET updated1 = update::<Account>(id1).set(acc.balance.increment_by(amount_to_transfer)); LET update2 = update::<Account>(id2).set(acc.balance.decrement_by(amount_to_transfer)); CANCEL TRANSACTION; }; Ok(()) }
In the code snippet above, the block!
macro is used to define a transaction
with multiple statements. The LET
statement is used to bind variables acc1
,
acc2
, updated1
, and update2
to the respective statements. The
BEGIN TRANSACTION
statement marks the start of the transaction, and the
CANCEL TRANSACTION
statement explicitly cancels the transaction.
The generated SQL query for this code block would look like:
BEGIN TRANSACTION;
LET acc1 = CREATE account CONTENT { balance: 135605.16, id: account:one };
LET acc2 = CREATE account CONTENT { balance: 91031.31, id: account:two };
LET updated1 = UPDATE account:one SET balance += 300.0;
LET update2 = UPDATE account:two SET balance -= 300.0;
CANCEL TRANSACTION;
Using the block!
macro with the cancel_transaction
statement within the
block provides a clear and concise way to define a transaction and cancel it.
Using block!
Macro for Chaining Multiple Statements
Another recommended approach is to use the block!
macro to chain multiple
statements together within a transaction. The cancel_transaction
statement is
called separately after the block!
macro to explicitly cancel the transaction.
Let's see an example:
#![allow(unused)] fn main() { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db ("test").await.unwrap(); let ref id1 = Account::create_id("one".into()); let ref id2 = Account::create_id("two".into()); let acc = Account::schema(); let amount_to_transfer = 300.00; let transaction_query = begin_transaction() .query(block! { LET acc1 = create().content(Account { id: id1.clone(), balance: 135_605.16, }); LET acc2 = create().content(Account { id: id2.clone(), balance: 91_031.31, }); LET updated1 = update::<Account>(id1).set(acc.balance.increment_by(amount_to_transfer)); LET update2 = update::<Account>(id2).set(acc.balance.decrement_by(amount_to_transfer)); }) .cancel_transaction(); transaction_query.run(db.clone()).await?; // Assertions and other code... Ok(()) }
In this approach, the block!
macro is used to define a transaction with
multiple statements. The LET
statement is used to bind variables to the
statements within the block. After the block!
macro, the cancel_transaction
statement is called separately to cancel the transaction.
The generated SQL query for this code block would be the same as the previous approach.
Using the block!
macro for chaining multiple statements and explicitly
canceling the transaction provides a structured and organized way to handle
complex transactions.
Less Recommended Approach
The less recommended approach involves chaining multiple statements directly
without using the block!
macro. Although functional, this approach may feel
less ergonomic, especially when there is a need to bind and share variables
within the statements.
Chaining Multiple Statements Directly
Here's an example of chaining multiple statements directly without using the
block!
macro:
#![allow(unused)] fn main() { #[tokio::test] async fn test_transaction_cancel_increment_and_decrement_update() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let ref id1 = Account::create_id("one".into()); let ref id2 = Account::create_id("two".into()); let amount_to_transfer = 300.00; let acc = Account::schema(); begin_transaction() .query(create().content(Account { id: id1.clone(), balance: 135_605.16, })) .query(create().content(Account { id: id2.clone(), balance: 91_031.31, })) .query(update::<Account>(id1).set(acc.balance.increment_by(amount_to_transfer))) .query(update::<Account>(id2).set(acc.balance.decrement_by(amount_to_transfer))) .cancel_transaction() .run(db.clone()) .await?; // Assertions and other code... Ok(()) } }
In this approach, multiple statements are chained directly within the
transaction. The create
and update
statements are used to perform operations
on the Account
table.
The generated SQL query for this code block would be the same as the previous approaches.
The less recommended approach of chaining multiple statements directly can be less ergonomic, especially when dealing with complex transactions that require variable bindings and subqueries.
It is generally recommended to use the recommended approaches with the block!
macro for better readability, automation of variable bindings, and subquery
handling.
That concludes the documentation for the cancel
statement in Surreal ORM. Use
the recommended approaches to perform transaction cancellation effectively.
Commit Statement
The commit
statement in Surreal ORM is used to commit a transaction and save
the changes made within the transaction. It ensures that the changes are durable
and permanent in the database.
Table of Contents
Recommended Approaches
Using block!
Macro with Commit Statement also Within Block for Chaining Multiple Statements
To perform a transaction and commit the changes, you can use the block!
macro
to chain multiple statements together. The commit_transaction
statement is
used within the block!
macro to explicitly indicate the commitment of the
transaction. Let's take a look at an example:
#![allow(unused)] fn main() { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let ref id1 = Account::create_id("one".into()); let ref id2 = Account::create_id("two".into()); let acc = Account::schema(); let amount_to_transfer = 300.00; block! { BEGIN TRANSACTION; LET acc1 = create().content(Account { id: id1.clone(), balance: 135_605.16, }); LET acc2 = create().content(Account { id: id2.clone(), balance: 91_031.31, }); LET updated1 = update::<Account>(id1).set(acc.balance.increment_by(amount_to_transfer)); LET update2 = update::<Account>(id2).set(acc.balance.decrement_by(amount_to_transfer)); COMMIT TRANSACTION; }; Ok(()) }
In the code snippet above, the block!
macro is used to define a transaction
with multiple statements. The LET
statement is used to bind variables acc1
,
acc2
, updated1
, and update2
to the respective statements. The
BEGIN TRANSACTION
statement marks the start of the transaction, and the
COMMIT TRANSACTION
statement explicitly commits the transaction.
The generated SQL query for this code block would look like:
BEGIN TRANSACTION;
LET acc1 = CREATE account CONTENT { balance: 135605.16, id: account:one };
LET acc2 = CREATE account CONTENT { balance: 91031.31, id: account:two };
LET updated1 = UPDATE account:one SET balance += 300.0;
LET update2 = UPDATE account:two SET balance -= 300.0;
COMMIT TRANSACTION;
Using the block!
macro with the commit_transaction
statement within the
block provides a clear and concise way to define a transaction and commit the
changes.
Using block!
Macro for Chaining Multiple Statements
Another recommended approach is to use the block!
macro to chain multiple
statements together within a transaction. The commit_transaction
statement is
called separately after the block!
macro to explicitly commit the transaction.
Let's see an example:
#![allow(unused)] fn main() { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let ref id1 = Account ::create_id("one".into()); let ref id2 = Account::create_id("two".into()); let acc = Account::schema(); let amount_to_transfer = 300.00; let transaction_query = begin_transaction() .query(block! { LET acc1 = create().content(Account { id: id1.clone(), balance: 135_605.16, }); LET acc2 = create().content(Account { id: id2.clone(), balance: 91_031.31, }); LET updated1 = update::<Account>(id1).set(acc.balance.increment_by(amount_to_transfer)); LET update2 = update::<Account>(id2).set(acc.balance.decrement_by(amount_to_transfer)); }) .commit_transaction(); transaction_query.run(db.clone()).await?; Ok(()) }
In this approach, the block!
macro is used to define a transaction block that
includes multiple statements. The BEGIN TRANSACTION
and COMMIT TRANSACTION
statements mark the start and end of the transaction, respectively. The LET
statement is used to bind variables to the statements within the block.
The generated SQL query for this code block would be the same as the previous approach.
Using the block!
macro for chaining multiple statements and explicitly
committing the transaction provides a more structured and organized way to
handle complex transactions.
Less Recommended Approach
The less recommended approach involves chaining multiple statements directly
without using the block!
macro. Although functional, this approach may feel
less ergonomic, especially when there is a need to bind and share variables
within the statements.
Chaining Multiple Statements Directly
Here's an example of chaining multiple statements directly without using the
block!
macro:
#![allow(unused)] fn main() { #[tokio::test] async fn test_transaction_commit_increment_and_decrement_update() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let ref id1 = Account::create_id("one".into()); let ref id2 = Account::create_id("two".into()); let amount_to_transfer = 300.00; let acc = Account::schema(); begin_transaction() .query(create().content(Account { id: id1.clone(), balance: 135_605.16, })) .query(create().content(Account { id: id2.clone(), balance: 91_031.31, })) .query(update::<Account>(id1).set(acc.balance.increment_by(amount_to_transfer))) .query(update::<Account>(id2).set(acc.balance.decrement_by(amount_to_transfer))) .commit_transaction() .run(db.clone()) .await?; // Assertions and other code... Ok(()) } }
In this approach, multiple statements are chained directly within the
transaction. The create
and update
statements are used to perform operations
on the Account
table.
The generated SQL query for this code block would be the same as the previous approaches.
The less recommended approach of chaining multiple statements directly can be less ergonomic, especially when dealing with complex transactions that require variable bindings and subqueries.
It is generally recommended to use the recommended approaches with the block!
macro for better readability, automation of variable bindings, and subquery
handling.
That concludes the documentation for the commit
statement in Surreal ORM. Use
the recommended approaches to perform transactions and commit changes
effectively.
IfElse Statement
The ifelse
statement is used to create conditional branching in SurrealDB. It allows you to execute different expressions or statements based on specified conditions. Here are some examples and usage scenarios for the ifelse
statement.
Table of Contents
- Statement Syntax
- Creating an If Statement
- Adding Else If Statements
- Adding an Else Statement
- Nested If Else Statements
- Using Subqueries in If Else Statements
Statement Syntax
The syntax for the ifelse
statement is as follows:
if_(condition)
.then(expression)
.else_if(condition)
.then(expression)
.else_if(condition)
.then(expression)
.else_(expression)
.end();
Creating an If Statement
You can create a simple if
statement using the if_
function. Here's an example:
#![allow(unused)] fn main() { use surreal_orm::*; let age = Field::new("age"); let if_statement = if_(age.greater_than_or_equal(18)) .then("Valid".to_string()) .end(); }
Adding Else If Statements
You can add multiple else if
statements to the ifelse
statement. Here's an example:
#![allow(unused)] fn main() { let name = Field::new("name"); let age = Field::new("age"); let if_statement = if_(age.greater_than_or_equal(18)) .then("Valid") .else_if(name.like("Oyelowo Oyedayo")) .then("The Alien!") .end(); }
Adding an Else Statement
You can add an else
statement to the ifelse
statement to handle cases when none of the previous conditions are met. Here's an example:
#![allow(unused)] fn main() { let age = Field::new("age"); let if_statement = if_(age.greater_than_or_equal(18)) .then("Valid") .else_("Invalid") .end(); }
Nested If Else Statements
You can nest ifelse
statements within each other to create complex conditional logic. Here's an example:
#![allow(unused)] fn main() { let name = Field::new("name"); let age = Field::new("age"); let country = Field::new("country"); let if_statement = if_(age.greater_than_or_equal(18)) .then("Valid") .else_if(name.like("Oyelowo Oyedayo")) .then("The Alien!") .else_if(cond(country.is("Canada")).or(country.is("Norway"))) .then("Cold") .else_("Hot") .end(); }
Using Subqueries in If Else Statements
You can use subqueries in the ifelse
statement to execute more complex expressions or statements. Here's an example:
#![allow(unused)] fn main() { let name = Field::new("name"); let age = Field::new("age"); let country = Field::new("country"); let city = Field::new("city"); let fake_id = sql::Thing::from(("user".to_string(), "oyelowo".to_string())); let fake_id2 = sql::Thing::from(("user".to_string(), "oyedayo".to_string())); let statement1 = select(All) .from(fake_id) .where_( cond(city.is("Prince Edward Island")) .and(city.is("NewFoundland")) .or(city.like("Toronto")), ) .order_by(order(&age).numeric()) .limit(153) .start(10) .parallel(); let statement2 = select(All) .from(fake_id2) .where_(country.is("INDONESIA")) .order_by(order(&age).numeric()) .limit(20) .start(5); let if_statement = if_(age.greater_than_or_equal(18).less_than_or_equal(120)) .then(statement1) .else_if(name.like("Oyelowo Oyedayo")) .then(statement2) .else_if(cond(country.is("Canada")) .or(country.is("Norway"))) .then("Cold") .else_("Hot") .end(); }
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)
.
Surreal ORM - Insertion Operations
Surreal ORM provides various options to perform data insertion operations in your database. This guide focuses on three main operations:
Table of Contents
Inserting Single Record
The ORM allows for inserting a single record into a database table. Below is an example of this:
#![allow(unused)] fn main() { // Required imports use surrealdb::Surreal; use surrealdb::engine::local::Mem; use surreal_models::Weapon; use surreal_orm::statements::insert; use chrono::Utc; // Initialize SurrealDB with the in-memory engine let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); // Define a single weapon let weapon = Weapon { name: String::from("Excalibur"), created: Utc::now(), strength: 1000, ..Default::default() }; // Insert the weapon into the database let created_weapon = insert(weapon).return_one(db.clone()).await.unwrap(); // Verify the inserted record assert_eq!(created_weapon.name, "Excalibur"); assert_eq!(created_weapon.strength, 1000); }
This code creates a single Weapon
record with the name "Excalibur" and a
strength of 1000.
Inserting Multiple Records
In addition to inserting single records, Surreal ORM also supports inserting multiple records at once. Here is an example:
#![allow(unused)] fn main() { // Required imports use surrealdb::Surreal; use surrealdb::engine::local::Mem; use surreal_models::Weapon; use surreal_orm::statements::insert; use chrono::Utc; // Initialize SurrealDB with the in-memory engine let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); // Define a list of weapons let weapons = (0..1000) .into_iter() .map(|i| Weapon { name: format!("Weapon{}", i), created: Utc::now(), strength: i, ..Default::default() }) .collect::<Vec<_>>(); // Insert the weapons into the database let created_weapons = insert(weapons).return_many(db.clone()).await.unwrap(); // Verify the inserted records assert_eq!(created_weapons.len(), 1000); assert_eq!(created_weapons[0].name, "Weapon0"); assert_eq!(created_weapons[0].strength, 0); }
This code creates 1000 Weapon
records with sequential names and strength
values.
Inserting from Another Table
Surreal ORM allows you to copy data from one table to another using the insert
statement. This is similar to creating a view in PostgreSQL, but instead of just
a projection, it's copying the data to a new table.
#![allow(unused)] fn main() { // Required imports use surrealdb::Surreal; use surrealdb::engine::local::Mem; use surreal_models::{Weapon, StrongWeapon}; use surreal_orm::statements::{insert, select, All, cond, order}; use chrono::Utc; // Initialize SurrealDB with the in-memory engine let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); // Define a list of weapons let weapons = ( 0..1000) .into_iter() .map(|i| Weapon { name: format!("Weapon{}", i), created: Utc::now(), strength: i, ..Default::default() }) .collect::<Vec<_>>(); // Insert the weapons into the database let created_weapons = insert(weapons).return_many(db.clone()).await.unwrap(); // Define a SELECT statement for weapons with strength values between 800 and 950 let weapon::Schema { strength, .. } = Weapon::schema(); let select_statement = select(All) .from(Weapon::table()) .where_(cond(strength.greater_than_or_equal(800)).and(strength.less_than(950))); // Insert the selected weapons into the StrongWeapon table let strong_weapons = insert::<StrongWeapon>(select_statement) .return_many(db.clone()) .await .unwrap(); // Verify the copied records assert_eq!(strong_weapons.len(), 150); assert_eq!(strong_weapons[0].strength, 800); }
This script inserts 1000 Weapon
records, selects those with strength values
between 800 and 950, and copies them into the StrongWeapon
table.
Create Statement
The create
statement is used to add new entries to the SurrealDB database. It
allows you to create records with specified content and set additional
properties for the query. This documentation provides an overview of the syntax
and usage of the create
statement.
Table of Contents
Syntax
The basic syntax of the create
statement is as follows:
#![allow(unused)] fn main() { create() .content(record_content) .set(set_statements) .return_type(return_types) .timeout(seconds) .parallel(); }
The create
statement supports the following methods:
.content(record_content)
: Specifies the content of the record to be created..set(set_statements)
: Sets the values of the fields to be updated in the record..return_type(return_types)
: Specifies the return type for the query..timeout(seconds)
: Sets the timeout duration for the query..parallel()
: Indicates whether the query should be executed in parallel.
Examples
Basic Create Statement with Content Method
To create a basic record using the create
statement, you can use the following
code:
#![allow(unused)] fn main() { let space_ship1 = create() .content(space_ship1.clone()) .get_one(db.clone()) .await?; }
This code will create a new entry for space_ship1
in the database.
Creating Linked Entities
The create
statement allows you to create entries that have links to other
entities. Here's an example of creating a linked entity:
#![allow(unused)] fn main() { let unsaved_alien = Alien { ... space_ships: LinkMany::from(vec![ created_spaceship1.clone(), created_spaceship2.clone(), space_ship3.clone(), ]), ... }; let created_alien_with_fetched_links = create() .content(unsaved_alien.clone()) .load_link_manys()? .return_one(db.clone()) .await?; }
In this example, unsaved_alien
is being created with links to three different
spaceships. The .load_link_manys()
method loads the linked entities in a
single statement.
Create Using the object!
Macro
The object!
macro provides a concise and type-safe way to specify values when
creating or updating records when using the set
method. It acts as syntactic
sugar for an array of setters but provides stricter field checking compared to
using a basic struct.
Here's an example showcasing its usage:
#![allow(unused)] fn main() { let spaceship_id_1 = SpaceShip::create_id("spaceship1".to_string()); let space_ship1 = create::<SpaceShip>() .set(object!(SpaceShip { id: spaceship_id_1, name: "SpaceShip1", created: Utc::now(), })) .get_one(db.clone()) .await?; assert_eq!(space_ship1.name, "SpaceShip1"); }
Using the object!
macro ensures all fields are provided and belong to the
specified struct. It also allows for using parameter
or field
as values.
This is recommended over using array of setters as shown next because of the
extra checks this provides.
Additionally, the object_partial!
macro functions similarly but allows for
omitting some fields. This is particularly useful for update statements where
only a subset of fields need to be changed.
#![allow(unused)] fn main() { let updated = update::<Weapon>(id) .set(object_partial!(Weapon { strength: 923u64 })) .return_one(db.clone()) .await?; }
Create with Set Method
You can use the set
method with the create
statement to set specific fields
of the record being created. The set
method supports multiple approaches for
specifying the setter statements:
- Using an array const (
&[T]
):
#![allow(unused)] fn main() { let space_ship2 = create::<SpaceShip>() .set([ id.equal_to(spaceship_id_2), name.equal_to("SpaceShip2".to_string()), created.equal_to(Utc::now()), ]) .get_one(db.clone()) .await?; }
- Using a
Vec
of setter statements:
#![allow(unused)] fn main() { let space_ship1 = create::<SpaceShip>() .set(vec![ id.equal_to(spaceship_id_1), name.equal_to("SpaceShip1".to_string()), created.equal_to(Utc::now()), ]) .get_one(db.clone()) .await?; }
In these examples, we demonstrate different ways to use the set
method. You
can use an array const ([T]
or &[T]
) or a Vec
to provide a list of setter
statements.
This concludes the documentation for the create
statement. Use this statement
to add new entries to the SurrealDB database with desired content and additional
properties.
Update Statement
The update
statement in Surreal ORM allows you to modify existing records in
your database. It provides various operations to update fields and perform
incremental changes to the data. This documentation provides an overview of the
syntax and usage of the update
statement, including the use of the object!
and object_partial!
macros for setting values.
Table of Contents
- Syntax
- Using the
cond!
Macro - Examples
Syntax
The basic syntax of the update
statement is as follows:
#![allow(unused)] fn main() { update::<Type>(id) .content(content) .merge(merge) .replace(replace) .set(settables) .patch(patch_op) .where_(condition) .return_type(return_type) .timeout(duration) .parallel(); }
The update
statement supports the following methods:
.content(content)
: Sets the content of the update statement..merge(merge)
: Performs a merge operation to update specific fields..replace(replace)
: Replaces the entire object with a new one..set(settables)
: Sets the values of the fields to be updated..patch(patch_op)
: Applies patch operations to the record..where_(condition)
: Adds a condition to the update statement..return_type(return_type)
: Specifies the desired return type for the query..timeout(duration)
: Sets the timeout duration for the query..parallel()
: Executes the query in parallel.
Note: Only one of the .content(), .merge(), .replace(), .set(), or .patch() methods can be used at a time.
Using the cond!
Macro
The cond!
macro provides a concise way to define conditions for update
operations. It enhances code readability while ensuring type safety.
Example:
#![allow(unused)] fn main() { let filter = cond!((strength > 5) && (strength < 15)); }
By using the cond!
macro, you can define conditions efficiently and
expressively for the update
statement.
For a more in-depth explanation and advanced usage of the cond!
macro,
refer to the dedicated chapter on helper macros.
Examples
Updating a Single Object
Using the Update Content
The update
statement also supports the content
method, which allows you to
specify the updated fields using a separate object. This provides a convenient
way to define the fields to be updated.
#![allow(unused)] fn main() { let created_weapon = create().content(weapon).get_one(db.clone()).await.unwrap(); let weapon_to_update = Weapon { name: "Oyelowo".to_string(), created: Utc::now(), strength: 1000, ..Default::default() }; let updated_weapon = update::<Weapon>(created_weapon.clone().id) .content(weapon_to_update) .get_one(db.clone()) .await?; }
In the above example, the content
method is used to specify the fields to be
updated in the created_weapon
object using the weapon_to_update
object.
Using the object!
and object_partial!
Macros with Set Operation
The set
method of the update
statement supports the object!
and
object_partial!
macros, providing a type-safe and concise way to specify
values when updating records. These macros offer several advantages:
- Type-safety: Both macros ensure that all fields provided belong to the specified struct.
- Parameters and Fields: They allow the use of
parameters
orfields
as values, providing flexibility in constructing dynamic update statements. - Use within Transactions: Especially within the
block!
macro for transactions, these macros can be invaluable as they allow dynamic field and parameter manipulations based on transactional logic.
Here's an example showcasing the usage of the object_partial!
macro with the
set
method:
#![allow(unused)] fn main() { let created_weapon = create().content(weapon).get_one(db.clone()).await.unwrap(); assert_eq!(created_weapon.name, "Laser"); assert_eq!(created_weapon.strength, 0); let ref id = created_weapon.clone().id; let weapon::Schema { strength, .. } = Weapon::schema(); update::<Weapon>(id) .set(object_partial!(Weapon { strength: 923u64 })) .return_one(db.clone()) .await?; let selected: Option<Weapon> = select(All) .from(Weapon::table()) .return_one(db.clone()) .await?; assert_eq!(selected.unwrap().strength, 923); }
In this example, the object_partial!
macro is used with the set
method to
update the strength
field of the Weapon
object. This approach offers the
advantages of type-safety and conciseness.
Here's an example showcasing the usage of the object!
macro with the set
method:
#![allow(unused)] fn main() { let created_weapon = create().content(weapon).get_one(db.clone()).await.unwrap(); let weapon::Schema { strength, name, .. } = Weapon::schema(); update::<Weapon>(created_weapon.clone().id) .set(object!(Weapon { strength: strength.increment_by(100u64), name: "UpgradedWeapon".to_string() })) .return_one(db.clone()) .await?; }
In this example, the object!
macro is used with the set
method to
simultaneously set the strength
field and rename the Weapon
. The macro
ensures that the fields provided belong to the Weapon
struct, providing
type-safety.
The primary difference between object!
and object_partial!
is completeness:
-
object!
Macro: This macro requires you to provide values for all fields of the struct. It's useful when you have values for all fields and want to ensure no fields are missed. -
object_partial!
Macro: This allows for specifying only a subset of fields. It's especially useful when you only want to update specific fields without having to specify all of them.
In practice, you'll choose between them based on the update requirements. If
you're updating all fields of a record and want to ensure none are missed,
object!
is preferable. If you're updating only certain fields,
object_partial!
offers a more concise approach.
Using the Set Operation
The update
statement also supports the set
method, which allows you to
perform 3 major kinds of updates including, overwriting a field with an
equal_to
method, increment
and decrement
method operations for numbers,
append
and remove
methods for arrays. All the arguments to these methods are
type-checked at compile- time to make sure they are valid for the respective
fields
- Use set method for a single field
#![allow(unused)] fn main() { let created_weapon = create().content(weapon).get_one(db.clone()).await.unwrap(); let weapon_to_update = Weapon { name: "Oyelowo".to_string(), created: Utc::now(), strength: 1000, ..Default::default() }; update::<Weapon>(weapon_to_update.id) .set(strength.increment_by(5u64)) .run(db.clone()) .await?; // You can even pass the entire model instance as an argument update::<Weapon>(weapon_to_update) .set(strength.increment_by(5u64)) .run(db.clone()) .await?; }
- Use set methods for updating multiple fields
#![allow(unused)] fn main() { update::<Weapon>(id) .set([ strength.increment_by(5u64), name.equal("Oyedayo"), ]) .run(db.clone()) .await?; // In addition to array const `[T]`,you can also use a `vec!`. update::<Weapon>(id) .set(vec![ strength.increment_by(5u64), name.equal("Oyedayo"), ]) .run(db.clone()) .await?; }
In the above example, the set
method is used to specify the fields to be
updated in the created_weapon
object using the weapon_to_update
object.
Using the Merge Operation
The merge
operation allows you to update a single object by merging new values
into the existing object. The new values overwrite the old ones, while fields
not present in the new object are unaffected.
#![allow(unused)] fn main() { let created_weapon = create().content(weapon).get_one(db.clone()).await.unwrap(); let weapon_to_update = Weapon { name: "Oyelowo".to_string(), created: Utc::now(), strength: 1000, ..Default::default() }; let updated_weapon = update::<Weapon>(created_weapon.clone().id) .merge(weapon_to_update) .get_one(db.clone()) .await?; }
In the above example, the merge
operation is used to update the
created_weapon
object with the fields from weapon_to_update
. The result is
stored in updated_weapon
.
Using the Replace Operation
The replace
operation allows you to replace an existing object entirely with a
new one. This operation removes all fields not present in the new object.
#![allow(unused)] fn main() { let created_weapon = create().content(weapon).get_one(db.clone()).await.unwrap(); let weapon_to_replace = Weapon { name: "Oyelowo".to_string(), created: Utc::now(), strength: 823, ..Default::default() }; let updated_weapon = update::<Weapon>(created_weapon.clone().id) .replace(weapon_to_replace) .get_one(db.clone()) .await?; }
In the above example, the replace
operation replaces the created_weapon
object with the fields from weapon_to_replace
. The result is stored in
updated_weapon
.
Using the Patch Operation
The patch
operation allows you to perform detailed modifications on fields
using methods such as patch_change
, patch_replace
, patch_remove
, and
patch_add
. It enables incremental changes to string fields, replacing field
values, removing fields, or adding new fields.
Using the Patch Add Operation
The patch_add
operation adds a new field to the object. It allows you to
include additional fields during the update.
- Applying single patch
#![allow(unused)] fn main() { let created_weapon = create().content(weapon).get_one(db.clone()).await.unwrap(); let updated_weapon = update::<Weapon>(created_weapon.clone().id) .patch(nice.patch_add(true)) .get_one(db.clone()) .await?; }
- Applying multiple patches
#![allow(unused)] fn main() { let ref _updated_weapon = update::<WeaponOld>(old_weapon.clone().id) .patch([nice.patch_add(true), bunchOfOtherFields.patch_add(56)]) .return_one(db.clone()) .await; }
In the above example, the patch_add
operation adds the nice
field with the
value true
to the created_weapon
object.
Using the Patch Replace Operation
The patch_replace
operation replaces the value of a field with a new value. It
allows you to update a field to a different value.
#![allow(unused)] fn main() { let created_weapon = create().content(weapon).get_one(db.clone()).await.unwrap(); let updated_weapon = update::<Weapon>(created_weapon.clone().id) .patch(strength.patch_replace(34u64)) .get_one(db.clone()) .await?; }
In the above example, the patch_replace
operation replaces the value of the
strength
field in the created_weapon
object with the specified value.
Using the Patch Remove Operation
The patch_remove
operation removes a field from the object entirely. This
operation is destructive, and the field will no longer be available after the
update. Make sure that the struct used here does not require that field to be
present. You can create a copy of the existing struct but without the new field.
#![allow(unused)] fn main() { let created_weapon = create().content(weapon).get_one(db.clone()).await.unwrap(); let updated_weapon = update::<Weapon>(created_weapon.clone().id) .patch(bunchOfOtherFields.patch_remove()) .get_one(db.clone()) .await?; }
In the above example, the patch_remove
operation removes the
bunchOfOtherFields
field from the created_weapon
object.
Using the Patch Change Operation
The patch_change
operation modifies part of a string field using the diff
format. It allows you to specify the changes to be applied to the field.
#![allow(unused)] fn main() { let created_weapon = create().content(weapon).get_one(db.clone()).await.unwrap(); let updated_weapon = update::<Weapon>(created_weapon.clone().id) .patch(name.patch_change("@@ -1,4 +1,4 @@\n te\n-s\n+x\n t\n")) .get_one(db.clone()) .await?; }
In the above example, the patch_change
operation modifies the name
field of
the created_weapon
object by changing "test" to "text".
Updating Multiple Objects
To update multiple objects, you can use the update
statement with a filter to
select the objects to update.
#![allow(unused)] fn main() { let filter = cond(strength.greater_than(5)).and(strength.less_than_or_equal(15)); let update_weapons_with_filter = update::<Weapon>(Weapon::table()) .content(Weapon { name: "Oyelowo".to_string(), created: Utc::now(), ..Default::default() }) .where_(filter) .return_many(db.clone()) .await?; }
In the above example, the update
statement updates all Weapon
objects that
meet the specified filter condition with the new values.
Please note that the above code snippets are for illustration purposes and may need to be adapted to your specific use case.
You have now learned how to use the update
statement to modify existing
records in your SurrealDB database. Use the various operations and methods
provided by the update
statement to perform precise updates and incremental
changes to your data.
Relate Statement
The relate
statement is used to create relationships between different
entities in SurrealDB. It allows you to establish connections and associate data
between tables. Here are some examples and usage scenarios for the relate
statement.
Table of Contents
- Getting Relations
- Valid ID Usage
- Invalid ID Usage
- Relate Subquery to Subquery
- Any Edge Filter
- Recursive Edge-to-Edge Connection
- Relate Query
- Relate Query with Subquery
- Using
set
Method withobject!
Macro in therelate
Statement - The Importance of the
object!
Macro in therelate
Statement
Getting Relations
You can retrieve the relations and aliases for a specific field in a struct
using the get_fields_relations_aliased
method. This example demonstrates how
to retrieve the relations and aliases for the Student
struct:
#![allow(unused)] fn main() { let relations_aliases = Student::get_fields_relations_aliased(); }
Valid ID Usage
To create a relationship between entities using valid IDs, you can use the
relate
statement. Here's an example of how to relate a student to a book:
#![allow(unused)] fn main() { let student_id = Student::create_id("1"); let book_id = Book::create_id("2"); let write_book = StudentWritesBook { time_written: Duration::from_secs(343), // other fields... }; let relation = relate(Student::with(&student_id).writes__(Empty).book(&book_id)) .content(write_book) .parallel(); }
Invalid ID Usage
When using invalid IDs in the relate
statement, errors will be generated.
Here's an example of relating entities with invalid IDs:
#![allow(unused)] fn main() { let student_id = Student::create_id("oye"); let book_id = Book::create_id("mars"); let write = StudentWritesBook { time_written: Duration::from_secs(343), // other fields... }; let relate_statement = relate(Student::with(&book_id).writes__(Empty).book(&student_id)) .content(write.clone()) .return_type(ReturnType::Before) .parallel(); }
Relate Subquery to Subquery
You can also use subqueries in the relate
statement to establish relationships
between subquery results. Here's an example:
#![allow(unused)] fn main() { let write = StudentWritesBook { time_written: Duration::from_secs(52), // other fields... }; let statement = relate( Student::with(select(All).from(Student::get_table())) .writes__(E) .book( select(All).from(Book::get_table()), ), ) .content(write.clone()); }
Any Edge Filter
The any_other_edges
function allows you to filter relationships based on
multiple edge types. Here's an example:
#![allow(unused)] fn main() { let aliased_connection = Student::with(student_id) .writes__(any_other_edges([visits, likes]).where_(timeWritten.less_than_or_equal(50))) .book(book_id) .__as__(Student::aliases().writtenBooks); }
Recursive Edge-to-Edge Connection
You can create recursive edge-to-edge connections using the relate
statement.
This allows you to select and relate entities at multiple levels. Here's an
example:
#![allow(unused)] fn main() { let aliased_connection = Student::with(student_id) .writes__(Empty) .writes__(Empty) .writes__(any_other_edges(&[writes, likes]).where_(timeWritten.less_than_or_equal(50))) .book(book_id) .__as__(Student::aliases().writtenBooks); }
Relate Query
The relate
statement can be used to execute a query and return the result.
Here's an example:
#![allow(unused)] fn main() { let relate_simple = relate(Student::with(student_id).writes__(E).book(book_id)).content(write); let relate_simple_object = relate_simple.return_one(db.clone()).await?; let relate_simple_array = relate_simple.return_many(db.clone()).await?; }
Relate Query with Subquery
You can also use subqueries in the relate
statement to execute more complex
queries. Here's an example:
#![allow(unused)] fn main() { let statement = relate( Student::with(select(All).from(Student::get_table())) .writes__(E) .book( select(All).from(Book::get_table()), ), ) .content(write.clone()); }
Using set
Method with object!
Macro in the relate
Statement
The relate
statement supports the use of the set
method, serving as an
alternative to the content
method for specifying data when creating
relationships between entities.
The set
method, when combined with the object!
macro, offers a concise,
type-safe, and robust way to define the fields to be set during the relation
creation. Using the object!
macro ensures that all fields are present, which
is crucial for avoiding serialization/deserialization issues arising from
missing fields or schema mismatches.
Example: Using object!
Macro with set
in relate
#![allow(unused)] fn main() { let student_id = Student::create_id("1"); let book_id = Book::create_id("2"); relate(Student::with(&student_id).writes__(Empty).book(&book_id)) .set(object!(StudentWritesBook { time_written: Duration::from_secs(343), // other fields... })) .parallel(); }
The Importance of the object!
Macro in the relate
Statement
In the context of the relate
statement, the object!
macro provides
significant advantages:
- Type Safety: The
object!
macro ensures type safety, drastically reducing the risk of type mismatches during compile-time. - Full Field Coverage: Ensures that all fields are present, protecting against potential issues during serialization/deserialization due to missing fields or schema mismatches.
- Readability and Clarity: Using the
object!
macro leads to cleaner code. By explicitly defining fields and their corresponding values, the code becomes more understandable. - Parameterized Fields: Supports the inclusion of parameters and fields,
making it especially valuable in transactional contexts within the
block!
macro.
Given these benefits, it's strongly recommended to utilize the object!
macro
in the relate
statement:
#![allow(unused)] fn main() { let relation_with_macro = relate(Student::with(&student_id).writes__(Empty).book(&book_id)) .set(object!({ timeWritten: Utc::now(), someOtherField: "Some Value", anotherField: "Another Value" })) .parallel(); }
Prioritizing the use of the object!
macro ensures a combination of safety,
clarity, and robustness in your development process.
Delete Operations
Table of Contents
- Setup and Test Data Creation
- Delete by ID Using Helper Functions
- Delete by ID
- Delete Using Model Instance
- Delete Using Conditions with Model Helper Functions
- Delete Multiple Records Based on Conditions
- Conclusion
Setup and Test Data Creation
Before diving into the deletion methods, let's set up the necessary environment and generate some test data.
#![allow(unused)] fn main() { use pretty_assertions::assert_eq; use surreal_models::{weapon, Weapon}; use surreal_orm::{ statements::{delete, insert}, *, }; use surrealdb::{ engine::local::{Db, Mem}, Surreal, }; async fn create_test_data(db: Surreal<Db>) -> Vec<Weapon> { let space_ships = (0..1000) .map(|i| Weapon { name: format!("weapon-{}", i), strength: i, ..Default::default() }) .collect::<Vec<Weapon>>(); insert(space_ships).return_many(db.clone()).await.unwrap() } }
Delete by ID Using Helper Functions
The surreal_orm
library provides helper functions on model instances for
common operations. Here's how you can delete a record using the delete_by_id
helper function:
#![allow(unused)] fn main() { #[tokio::test] async fn test_delete_by_id_helper_function() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let weapons = create_test_data(db.clone()).await; let weapon1 = weapons.first().unwrap(); let ref weapon1_id = weapon1.id.clone(); let weapon::Schema { id, .. } = &Weapon::schema(); let deleted_weapon_count = || async { Weapon::count_where(id.eq(weapon1_id)) .get(db.clone()) .await .unwrap() }; assert_eq!(deleted_weapon_count().await, 1); Weapon::delete_by_id(weapon1_id).run(db.clone()).await?; assert_eq!(deleted_weapon_count().await, 0); Ok(()) } }
Delete by ID
Another approach to delete a record is by directly using its ID. This method is efficient for deleting a single record:
#![allow(unused)] fn main() { #[tokio::test] async fn test_delete_one_by_id() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let weapons = create_test_data(db.clone()).await; let weapon1 = weapons.first().unwrap(); let ref weapon1_id = weapon1.id.clone(); let weapon::Schema { id, .. } = &Weapon::schema(); let deleted_weapon_count = || async { Weapon::count_where(id.eq(weapon1_id)) .get(db.clone()) .await .unwrap() }; assert_eq!(deleted_weapon_count().await, 1); delete::<Weapon>(weapon1_id).run(db.clone()).await?; assert_eq!(deleted_weapon_count().await, 0); Ok(()) } }
Delete Using Model Instance
Rather than specifying an ID or condition, surreal_orm
allows developers to
delete records directly using a model instance. This approach can be useful when
the developer already has a reference to the model instance they want to delete:
#![allow(unused)] fn main() { #[tokio::test] async fn test_delete_one_by_model_instance() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let weapons = create_test_data(db.clone()).await; let weapon1 = weapons.first().unwrap(); let ref weapon1_id = weapon1.id.clone(); let weapon::Schema { id, .. } = &Weapon::schema(); let deleted_weapon_count = || async { Weapon::count_where(id.eq(weapon1_id)) .get(db.clone()) .await .unwrap() }; let deleted_weapon = || async { Weapon::find_by_id(weapon1_id) .return_one(db.clone()) .await .unwrap() }; assert_eq!(deleted_weapon().await.is_some(), true); assert_eq!(deleted_weapon_count().await, 1); weapon1.delete().run(db.clone()).await?; assert_eq!(deleted_weapon().await.is_some(), false); assert_eq!(deleted_weapon_count().await, 0); Ok(()) } }
Delete Using Conditions with Model Helper Functions
Sometimes, developers may need to delete a group of records based on a particular condition. Model helper functions can also facilitate such operations:
#![allow(unused)] fn main() { #[tokio::test] async fn test_delete_where_model_helper_function() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); create_test_data(db.clone()).await; let weapon::Schema { strength, .. } = &Weapon::schema(); let weapons_count = || async { Weapon::count_all().get(db.clone()).await.unwrap() }; assert_eq!(weapons_count().await, 1000); Weapon::delete_where(cond(strength.gte(500)).and(strength.lt(600))) .run(db.clone()) .await?; assert_eq!(weapons_count().await, 900); Ok(()) } }
Delete Multiple Records Based on Conditions
The ORM also provides direct deletion methods for multiple records based on specific conditions. This is particularly useful when the developer knows the exact criteria they want to match for the deletion:
#![allow(unused)] fn main() { #[tokio::test] async fn test_delete_many_query_by_condition() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); create_test_data(db.clone()).await; let weapon::Schema { strength, .. } = &Weapon::schema(); let weapons_count = || async { Weapon::count_all().get(db.clone()).await.unwrap() }; assert_eq!(weapons_count().await, 1000); delete::<Weapon>(Weapon::table()) .where_(cond(strength.gte(500)).and(strength.lt(600))) .run(db.clone()) .await?; assert_eq!(weapons_count().await, 900); Ok(()) } }
Conclusion
The delete operations in surreal_orm
offer a flexible and comprehensive
mechanism to remove records from the surrealdb
database. Whether it's deleting
a single record using its ID, removing multiple records based on conditions, or
even utilizing model instances for deletions, the ORM provides an arsenal of
tools to help developers manage their data efficiently.
For Statement (Permissions)
The for
statement is used to define permissions for a specific action or CRUD
operation in SurrealDB. It allows you to specify the desired permissions and
conditions for the action. This statement is commonly used when defining tables
or fields in SurrealDB, but it may also be used for access control for other
objects in the future. This documentation provides an overview of the syntax and
usage of the for
statement.
Table of Contents
Syntax
The basic syntax of the for
statement is as follows:
#![allow(unused)] fn main() { for_permission(permission_type) .where_(condition); }
permission_type
: The type of permission or action for which you want to define permissions. It can be a single permission type or an array of permission types.condition
: The condition or criteria for the permission. It specifies the conditions under which the permission should be granted.
The for
statement supports the following methods:
.where_(condition)
: Specifies the condition or criteria for the permission.
Permission Types
SurrealDB uses permission types to define different actions or CRUD operations that can be performed on tables or fields. Here are the available permission types:
Create
: Grants permission to create new records or objects.Read
(orSelect
): Grants permission to read or retrieve data from records or objects.Update
: Grants permission to modify or update existing records or objects.Delete
: Grants permission to delete records or objects.
These permission types allow you to define fine-grained access control for different actions in your database.
Using the cond!
Macro
The cond!
macro provides an alternative way to the cond
function way to
define conditions for the for
statement when specifying the condition for the
permissions. With the cond!
macro, you can easily specify conditions that
determine when permissions are granted.
For instance:
#![allow(unused)] fn main() { let condition = cond!((field_name OR "value") OR (age > 18)); }
The above code checks if the field named "field_name" equals the string "value".
For more details on the cond!
macro,
refer to the dedicated chapter on helper macros.
Examples
Define Permission for Single Action
To define permissions for a single action, you can use the following code:
#![allow(unused)] fn main() { use CrudType::*; let name = Field::new("name"); let for_res = for_permission(Create).where_(name.like("Oyelowo")); println!("{}", for_res.to_raw().build()); }
The above code will generate the following raw statement:
FOR create
WHERE name ~ 'Oyelowo'
In the example above, the for
statement defines permissions for the Create
action. It specifies the condition that the field "name" should be matched with
the pattern "Oyelowo". This means that the permission to create records will be
granted only when the field "name" matches the pattern.
Define Permissions for Multiple Actions (Individual)
To define permissions for multiple actions individually, you can use the following code:
#![allow(unused)] fn main() { use CrudType::*; let name = Field::new("name"); let for_res = for_permission(Select).where_(age.greater_than_or_equal(18)) .permissions(for_permission(Create).where_(name.is("Oyedayo"))) .permissions(for_permission(Update).where_(age.less_than_or_equal(130))); println!("{}", for_res.to_raw().build()); }
The above code will generate the following raw statement:
FOR select
WHERE age >= 18
PERMISSIONS
FOR create
WHERE name IS 'Oyedayo'
FOR update
WHERE age <= 130
In the example above, the for
statement defines permissions for the Select
action, as well as individual permissions for the Create
and Update
actions.
It specifies different conditions for each action. This means that the
permissions for these actions will be granted only when the specified conditions
are met.
Define Permissions for Multiple Actions (Array)
To define permissions for multiple actions using an array, you can use the following code:
#![allow(unused)] fn main() { use CrudType::*; let name = Field::new("name"); let for_res = for_permission(&[Create, Delete, Select, Update]).where_(name.is("Oyedayo")); println!("{}", for_res.to_raw().build()); }
The above code will generate the following raw statement:
FOR create, delete, select, update
WHERE name IS 'Oyedayo'
In the example above, the for
statement defines permissions for multiple
actions (Create
, Delete
, Select
, and Update
) using an array. It
specifies a common condition for all the actions. This means that the
permissions for these actions will be granted only when the field "name" is
equal to "Oyedayo".
Define Permissions for Multiple Actions (Mixed)
To define permissions for multiple actions using a mix of individual permissions and an array, you can use the following code:
#![allow(unused)] fn main() { use CrudType::*; let name = Field::new("name"); let for_res = for_permission(&[Create, Delete]).where_(name.is("Oyedayo")) .permissions(for_permission(Update).where_(age.less_than_or_equal(130))); println!("{}", for_res.to_raw().build()); }
The above code will generate the following raw statement:
FOR create, delete
WHERE name IS 'Oyedayo'
PERMISSIONS
FOR update
WHERE age <= 130
In the example above, the for
statement defines individual permissions for the
Create
and Delete
actions, and an array of permissions for the Update
action. It specifies different conditions for each action. This means that the
permissions for these actions will be granted only when the specified conditions
are met.
You have now learned how to define permissions using the for
statement in
SurrealDB. Use this statement to specify the desired access control for
different actions or CRUD operations in your database. While it is commonly used
when defining tables or fields, it may also be utilized for access control for
other objects in the future.
Define Statement
The define
statement in SurrealDB is a powerful tool that allows you to define various objects and configurations within the database. It provides a flexible and expressive way to create and manage entities such as tables, indexes, namespaces, tokens, logins, and more. This documentation provides an overview of the define
statement and its usage.
Table of Contents
Introduction
The define
statement serves as a declarative mechanism for defining and configuring various elements in SurrealDB. It enables you to specify the properties and characteristics of different objects, helping you define the structure, behavior, and access controls of your database components.
By using the define
statement, you can create and manage objects such as tables, indexes, namespaces, tokens, logins, and more, all within a single comprehensive syntax. This provides a unified approach to defining and organizing your database entities, making it easier to maintain and modify them over time.
Syntax
The general syntax of the define
statement is as follows:
#![allow(unused)] fn main() { define(object_name) .property1(value1) .property2(value2) .property3(value3) // ... }
The specific properties and values depend on the type of object being defined. Each object may have different properties that can be set, such as names, types, constraints, configurations, and more. The define
statement provides a fluent and chainable API to set these properties in a concise and readable manner.
Supported Objects
The define
statement supports a variety of objects that can be defined within SurrealDB. Some of the commonly used objects include:
- Tables: Define the structure and schema of tables within the database.
- Indexes: Define indexes on tables to optimize data retrieval and querying.
- Namespaces: Define logical containers to organize database objects.
- Tokens: Define authentication and authorization tokens for access control.
- Logins: Define user logins for authentication purposes.
- Scopes: Define scopes to encapsulate and manage query execution environments.
These are just a few examples of the objects that can be defined using the define
statement. SurrealDB provides a rich set of features and options for each object type, allowing you to customize and tailor the behavior of your database entities according to your specific requirements.
Examples
Here are a few examples of using the define
statement to define different objects:
- Defining a table:
#![allow(unused)] fn main() { let user = Table::from("user"); let statement = define_table(user).schemaless().permissions_full(); }
- Defining an index:
#![allow(unused)] fn main() { let query = define_index("userEmailIndex") .on_table(User::table()) .fields(email) .unique(); }
- Defining a namespace:
#![allow(unused)] fn main() { let namespace_def = define_namespace("myapp"); }
- Defining a token:
#![allow(unused)] fn main() { let token_def = define_token("access_token") .on_namespace() .type_(TokenType::HS256) .value("mysecretpassword"); }
These examples showcase the versatility and power of the define
statement in SurrealDB. You can define and configure a wide range of objects using a consistent and intuitive syntax, enabling you to shape your database according to your desired structure and requirements.
This concludes the overview of the define
statement in SurrealDB. You can now leverage its capabilities to define and manage various objects within your database, providing a solid foundation
for building robust and scalable applications.
Define Namespace Statement
The define_namespace
statement is used to define a namespace in SurrealDB. A namespace is a logical container for organizing database objects, such as tables, indexes, and functions. This documentation provides an overview of the syntax and usage of the define_namespace
statement.
Table of Contents
Syntax
The syntax of the define_namespace
statement is as follows:
#![allow(unused)] fn main() { define_namespace(namespace_name: &str) }
namespace_name
: The name of the namespace to define.
Examples
Define a Namespace
To define a namespace, you can use the following code:
#![allow(unused)] fn main() { let statement = define_namespace("oyelowo"); }
In the example above, the define_namespace
statement defines a namespace named "oyelowo".
This will generate the following SQL statement:
DEFINE NAMESPACE oyelowo;
You have now learned how to define a namespace using the define_namespace
statement.
Namespaces provide a way to organize and structure your database objects within SurrealDB,
enabling better management and organization of your resources.
Define Database Statement
The define_database
statement is used to define a database in SurrealDB. A database is a logical container for storing related data and organizing resources. This documentation provides an overview of the syntax and usage of the define_database
statement.
Table of Contents
Syntax
The syntax of the define_database
statement is as follows:
#![allow(unused)] fn main() { define_database(database_name: Database) }
database_name
: The name of the database to define.
Examples
Define a Database
To define a database, you can use the following code:
#![allow(unused)] fn main() { let statement = define_database("oyelowo"); }
In the example above, the define_database
statement defines a database named "oyelowo".
This will generate the following SQL statement:
DEFINE DATABASE oyelowo;
You have now learned how to define a database using the define_database
statement. Databases provide a way to organize and manage data within SurrealDB, allowing you to create distinct containers for your data resources.
Define Login Statement
The define_login
statement is used to define a login in SurrealDB. Logins are used for authentication purposes, allowing users to authenticate and access protected resources. This documentation provides an overview of the syntax and usage of the define_login
statement.
Table of Contents
Syntax
The basic syntax of the define_login
statement is as follows:
#![allow(unused)] fn main() { define_login(login_name: Login) .on_namespace() .password(password: &str) define_login(login_name: Login) .on_database() .password(password: &str) define_login(login_name: Login) .on_namespace() .passhash(passhash: &str) }
login_name
: The name of the login to define.password
: The password associated with the login.passhash
: The password hash associated with the login.
The define_login
statement supports the following options:
on_namespace()
: Specifies that the login should be defined on the namespace level.on_database()
: Specifies that the login should be defined on the database level.
Examples
Define Login with Password
To define a login with a password, you can use the following code:
#![allow(unused)] fn main() { let username = Login::new("username"); let login_with_password = define_login(username) .on_database() .password("oyelowo"); }
In the example above, the define_login
statement defines a login named "username" on the database level. The login is associated with a password "oyelowo".
This will generate the following SQL statement:
DEFINE LOGIN username ON DATABASE PASSWORD 'oyelowo';
Define Login with Passhash
To define a login with a password hash, you can use the following code:
#![allow(unused)] fn main() { let login_with_hash = define_login("username") .on_namespace() .passhash("reiiereroyedayo"); }
In the example above, the define_login
statement defines a login named "username" on the namespace level. The login is associated with a password hash "reiiereroyedayo".
This will generate the following SQL statement:
DEFINE LOGIN username ON NAMESPACE PASSHASH 'reiiereroyedayo';
You have now learned how to define logins using the define_login
statement. Logins are essential for authentication in SurrealDB, allowing users to securely access protected resources.
Define Token Statement
The define_token
statement is used to define a token in SurrealDB. Tokens are used for authentication and authorization purposes, allowing users or applications to access protected resources. This documentation provides an overview of the syntax and usage of the define_token
statement.
Table of Contents
Syntax
The basic syntax of the define_token
statement is as follows:
#![allow(unused)] fn main() { define_token(token_name: Token) .on_namespace() .type_(token_type: TokenType) .value(token_value: &str) define_token(token_name: Token) .on_database() .type_(token_type: TokenType) .value(token_value: &str) define_token(token_name: Token) .on_scope(scope_name: Scope) .type_(token_type: TokenType) .value(token_value: &str) }
token_name
: The name of the token to define.token_type
: The type of the token, specified using theTokenType
enum.token_value
: The value or secret associated with the token.
The define_token
statement supports the following options:
on_namespace()
: Specifies that the token should be defined on the namespace level.on_database()
: Specifies that the token should be defined on the database level.on_scope(scope_name)
: Specifies that the token should be defined on a specific scope.
Examples
Define Token on Namespace
To define a token on the namespace level, you can use the following code:
#![allow(unused)] fn main() { let statement = define_token("oyelowo_token") .on_namespace() .type_(TokenType::PS512) .value("abrakradabra"); }
In the example above, the define_token
statement defines a token named "oyelowo_token" on the namespace level. The token type is set to TokenType::PS512
and the value is set to "abrakradabra".
This will generate the following SQL statement:
DEFINE TOKEN oyelowo_token ON NAMESPACE TYPE PS512 VALUE 'abrakradabra';
Define Token on Database
To define a token on the database level, you can use the following code:
#![allow(unused)] fn main() { let statement = define_token("oyelowo_token") .on_database() .type_(TokenType::HS512) .value("anaksunamun"); }
In the example above, the define_token
statement defines a token named "oyelowo_token" on the database level. The token type is set to TokenType::HS512
and the value is set to "anaksunamun".
This will generate the following SQL statement:
DEFINE TOKEN oyelowo_token ON DATABASE TYPE HS512 VALUE 'anaksunamun';
Define Token on Scope
To define a token on a specific scope, you can use the following code:
#![allow(unused)] fn main() { let statement = define_token("oyelowo_token") .on_scope("planet") .type_(TokenType::EDDSA) .value("abcde"); }
In the example above, the define_token
statement defines a token named "oyelowo_token" on the scope "planet". The token type is set to TokenType::EDDSA
and the value is set to "abcde".
This will generate the following SQL
statement:
DEFINE TOKEN oyelowo_token ON SCOPE planet TYPE EDDSA VALUE 'abcde';
Token Types
The TokenType
enum represents the available token types in SurrealDB. Each token type corresponds to a specific algorithm or cryptographic scheme used for token generation and validation. The following token types are available:
EDDSA
: EdDSA (Edwards-curve Digital Signature Algorithm)ES256
: ECDSA using P-256 and SHA-256ES384
: ECDSA using P-384 and SHA-384ES512
: ECDSA using P-521 and SHA-512HS256
: HMAC using SHA-256HS384
: HMAC using SHA-384HS512
: HMAC using SHA-512PS256
: RSASSA-PSS using SHA-256 and MGF1 with SHA-256PS384
: RSASSA-PSS using SHA-384 and MGF1 with SHA-384PS512
: RSASSA-PSS using SHA-512 and MGF1 with SHA-512RS256
: RSASSA-PKCS1-v1_5 using SHA-256RS384
: RSASSA-PKCS1-v1_5 using SHA-384RS512
: RSASSA-PKCS1-v1_5 using SHA-512
You can specify the desired token type when using the define_token
statement by providing the corresponding TokenType
enum variant.
You have now learned how to define tokens using the define_token
statement. Tokens are essential for authentication and authorization in SurrealDB, allowing you to secure your data and control access to resources.
Define Scope Statement
The define_scope
statement is used to define a scope in SurrealDB. Scopes
provide a way to encapsulate a set of operations within a specific context or
namespace. This documentation provides an overview of the syntax and usage of
the define_scope
statement.
Table of Contents
Syntax
The basic syntax of the define_scope
statement is as follows:
#![allow(unused)] fn main() { define_scope(scope_name: &str) { // Scope definition } }
scope_name
: The name of the scope to define.
The define_scope
statement supports the following features:
- Defining session duration for the scope.
- Defining operations for the scope, such as signup and signin.
Examples
Define Scope on Namespace
To define a scope on a namespace with signup and signin operations, you can use the following code:
#![allow(unused)] fn main() { block! { let user::Schema { email, pass } = &User::schema(); let email = "oyelowo@codebreather.com"; let password = "very-strong"; let token_def = define_scope("oyelowo_scope") .session(Duration::from_secs(45)) .signup( create::<User>() .set(vec![ email.equal_to(email), pass.equal_to(crypto::argon2::generate!(password)), ]) ) .signin( select(All).from(User::table()).where_( cond(email.equal(email)) .and(crypto::argon2::compare!(pass, password)), ), ); } }
In the example above, the define_scope
statement defines a scope named
"oyelowo_scope" on the namespace. The scope includes a session duration of 45
seconds. It also defines signup and signin operations within the scope. The
signup operation uses the create
statement with a non-raw query to create a
new user record. The email
and pass
fields are set using parameter
placeholders. The pass
field is generated using the crypto::argon2::generate
function. The signin operation performs a select query with conditions.
This will generate the following SQL statement:
DEFINE SCOPE oyelowo_scope SESSION 45s
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($password) )
SIGNIN ( SELECT * FROM user WHERE (email = email) AND (crypto::argon2::compare(pass, $password)) );
You can then use the defined scope in your queries by referencing the scope name.
Now you have learned how to define a scope using the define_scope
statement.
Scopes provide a way to encapsulate a set of operations within a specific
context or namespace. Refer to the SurrealDB documentation for more information
on scopes and their usage.
Define Table Statement
The define_table
statement is used to define a table in SurrealDB. It allows you to specify various options and permissions for the table. This documentation provides an overview of the syntax and usage of the define_table
statement.
Table of Contents
Syntax
The basic syntax of the define_table
statement is as follows:
#![allow(unused)] fn main() { define_table(table) .drop() .as_(select_statement) .schemafull() .permissions(permission_statements); }
table
: The name of the table to define.
The define_table
statement supports the following methods:
.drop()
: Drops the existing table before defining it..as_(select_statement)
: Specifies aSELECT
statement to populate the table..schemafull()
: Defines the table with a schema..permissions(permission_statements)
: Specifies the permissions for the table.
Examples
Schemaless Table
To define a schemaless table with no permissions, you can use the following code:
#![allow(unused)] fn main() { let user = Table::from("user"); let statement = define_table(user).schemaless().permissions_none(); }
This will generate the following SQL statement:
DEFINE TABLE user SCHEMALESS PERMISSIONS NONE;
Schemaless Table with Permissions
To define a schemaless table with full permissions, you can use the following code:
#![allow(unused)] fn main() { let user = Table::from("user"); let statement = define_table(user).schemaless().permissions_full(); }
This will generate the following SQL statement:
DEFINE TABLE user SCHEMALESS PERMISSIONS FULL;
Define Table with Projection
A projection allows you to define a table based on a subset of columns from another table. It is similar to creating a view in a relational database. You can specify a projection using the as_
method and provide a SELECT
statement as the projection definition. The selected columns and rows will be used to populate the defined table.
Here's an example that demonstrates how to define a table with a projection:
#![allow(unused)] fn main() { let user_table = Table::from("user"); let projection_statement = select(All).from(user_table).where_(age.greater_than(18)); let statement = define_table(user_table).as_(projection_statement); }
This will generate the following SQL statement:
DEFINE TABLE user AS SELECT * FROM user WHERE age > 18;
In the example above, the define_table
statement defines a table named "user" with a projection based on a SELECT
statement. Only the rows that satisfy the condition age > 18
will be included in the table.
Define Table with Multiple Permissions
You can define a table with multiple permissions using the permissions
method. The following example demonstrates various permission configurations:
#![allow(unused)] fn main() { let name = Field::new("name"); let user_table = Table::from("user"); let age = Field::new("age"); let country = Field::new("country"); let fake_id2 = sql::Thing::from(("user".to_string(), "oyedayo".to_string())); let statement = define_table(user_table) .drop() .as_( select(All) .from(fake_id2) .where_(country.is("INDONESIA")) .order_by(order(&age).numeric().desc()) .limit(20) .start(5), ) .schemafull() .permissions(for_permission(Select).where_(age.greater_than_or_equal(18))) // Single works .permissions(for_permission([Create, Delete]).where_(name.is("Oyedayo"))) // Multiple .permissions([ for_permission([Create, Delete]).where_(name.is("Oyedayo")), for_permission(Update).where_(age.less_than_or_equal(130)), ]); }
This will generate the following SQL statement:
DEFINE TABLE user DROP SCHEMAFULL AS
SELECT * FROM user:oyedayo
WHERE country IS 'INDONESIA' ORDER BY age NUMERIC DESC
LIMIT 20 START AT 5
PERMISSIONS
FOR select
WHERE age >= 18
FOR create, delete
WHERE name IS 'Oyedayo'
FOR create, delete
WHERE name IS 'Oyedayo'
FOR update
WHERE age <= 130;
In the example above, the define_table
statement defines a table named "user". It drops the existing table, populates it with data from a SELECT
statement, and sets various permissions based on conditions.
This concludes the documentation for the define_table
statement. Use this statement to define tables in SurrealDB and specify the desired permissions, configurations, and projections.
Define Event Statement
The define_event
statement is used to define an event in SurrealDB. It allows
you to specify the conditions and actions associated with the event. This
documentation provides an overview of the syntax and usage of the define_event
statement.
Table of Contents
Syntax
The basic syntax of the define_event
statement is as follows:
#![allow(unused)] fn main() { define_event(event_name) .on_table(table) .when(condition) .then(action); }
event_name
: The name of the event to define.table
: The name of the table where the event occurs.condition
: The condition that triggers the event.action
: The action to perform when the event is triggered.
The define_event
statement supports the following methods:
.on_table(table)
: Specifies the table where the event occurs..when(condition)
: Specifies the condition that triggers the event..then(action)
: Specifies the action to perform when the event is triggered.
Using the cond!
Macro
The cond!
macro is a handy tool when defining conditions for the WHEN
clause
in the DEFINE EVENT
statement. It provides a concise way to define conditions,
enhancing readability while ensuring type safety.
Example:
#![allow(unused)] fn main() { let filter = cond!((strength > 5) && (strength < 15)); }
By using the cond!
macro, you can effectively and expressively define
conditions for the DEFINE EVENT
statement.
For a more in-depth explanation and advanced usage of the cond!
macro,
refer to the dedicated chapter on helper macros.
Examples
Define Event with State Machine
To define an event with a state machine-like behavior, you can use the following code:
#![allow(unused)] fn main() { let age = Field::new("age"); let city = Field::new("city"); let fake_id = sql::Thing::from(("user".to_string(), "oyelowo".to_string())); let user_table = Table::new("user"); let email_event = Event::new("email"); let query = define_event(email_event) .on_table(user_table) .when(cond(age.greater_than_or_equal(18))) .then( select(All) .from(fake_id) .where_( cond(city.is("Prince Edward Island")) .and(city.is("NewFoundland")) .or(city.like("Toronto")), ) .limit(153) .start(10) .parallel(), ); }
This will generate the following SQL statement:
DEFINE EVENT email ON TABLE user WHEN age >= 18 THEN SELECT * FROM user:oyelowo WHERE (city IS 'Prince Edward Island') AND (city IS 'NewFoundland') OR (city ~ 'Toronto') LIMIT 153 START AT 10 PARALLEL;
In the example above, the define_event
statement defines an event named
"email" on the "user" table. It specifies that the event is triggered when the
age is greater than or equal to 18. The action associated with the event is to
perform a SELECT
query on the "user:oyelowo" table with certain conditions and
settings.
This concludes the documentation for the define_event
statement. Use this
statement to define events in SurrealDB and specify their conditions and
actions.
Define Function Statement
The define_function!
statement is used to define a custom function in
SurrealDB. It allows you to define reusable logic that can be used within
queries. This documentation provides an overview of the syntax and usage of the
define_function!
statement.
Table of Contents
Syntax
The basic syntax of the define_function!
statement is as follows:
#![allow(unused)] fn main() { define_function!(function_name(parameter1: type1, parameter2: type2, ...) { // Function logic }); }
function_name
: The name of the function to define.parameter1
,parameter2
, ...: The parameters of the function, along with their types.function logic
: The logic or operations to be performed by the function.
The define_function!
statement supports the following features:
- Defining function parameters and their types.
- Writing custom logic or operations within the function body.
- Returning values from the function.
Examples
Define Function with Parameters and Logic
To define a function with parameters and custom logic, you can use the following code:
#![allow(unused)] fn main() { define_function!(get_it(first: bool, last: string, birthday: string) { let person = "43"; return person; }); }
In the example above, the define_function!
statement defines a function named
"get_it" with three parameters: first
, last
, and birthday
. The function
body consists of assigning a value to the person
variable and returning it.
This will generate the following SQL statement:
DEFINE FUNCTION get_it($first: bool, $last: string, $birthday: string) {
LET $person = '43';
RETURN $person;
};
You can then use the defined function in queries by calling it with the appropriate arguments.
Define Function with Complex Logic
Here's an example of defining a function with more complex logic and operations:
#![allow(unused)] fn main() { use surreal_models::SpaceShip; use surreal_orm::{ cond, index, statements::{create, define_function, if_, select}, All, Buildable, Operatable, SchemaGetter, SetterAssignable, Model, ToRaw, NONE, }; define_function!(get_person(first_arg: string, last_arg: string, birthday_arg: string) { let person = select(All) .from(SpaceShip::table()) .where_( cond(SpaceShip::schema().id.equal(&first_arg)) .and(SpaceShip::schema().name.equal(&last_arg)) .and(SpaceShip::schema().created.equal(&birthday_arg)), ); return if_(person.with_path::<SpaceShip>(index(0)).id.is_not(NONE)) .then(person.with_path::<SpaceShip>(index(0))) .else_( create::<SpaceShip>().set( vec![ SpaceShip::schema().id.equal_to(&first_arg), SpaceShip::schema().name.equal_to(&last_arg), SpaceShip::schema().created.equal_to(&birthday_arg), ] ) ).end(); }); }
In the example above, the define_function!
statement defines a function named
"get_person" with three parameters: first_arg
, last_arg
, and birthday_arg
.
The function body consists of a complex logic that includes a SELECT statement,
conditional checks, and the creation of a new record if the condition is not
met.
This will generate the following SQL statement:
DEFINE FUNCTION get_person($first_arg: string, $last_arg: string, $birthday_arg: string) {
LET $person = (SELECT * FROM space_ship WHERE (id = $first_arg) AND (name = $last_arg) AND (created = $birthday_arg));
RETURN IF $person[0].id != NONE THEN $person[0] ELSE (CREATE space_ship SET id = $first_arg, name = $last_arg, created = $birthday_arg) END;
};
You can then use the defined function in queries by calling it with the appropriate arguments.
Using the Generated Function
To use the function defined using define_function!
, you need to execute the
generated statement before you can use the function in your queries. The
generated statement is suffixed by _statement
and contains the actual function
definition. After executing the statement, you can use the function without the
_statement
suffix.
Here's an example of how to use the defined function:
#![allow(unused)] fn main() { // Define the function statement let fn_statement = get_it_statement(); // Execute the statement to define the function // This statement needs to be executed before the function can be used fn_statement.run(db); // Use the defined function in a query let get_it_function = get_it(false, "3".to_string(), "3".to_string()); // Verify the generated function can be used in a query assert_eq!(get_it_function.to_raw().build(), "get_it(false, '3', '3')"); assert_eq!( get_it_function.fine_tune_params(), "et_it($_param_00000001, $_param_00000002, $_param_00000003)" ); }
In this example, we first define the function statement using the
get_it_statement()
macro. Then, we execute the generated statement using
surreal_orm::execute()
to define the function in SurrealDB. After that, we can
use the defined function get_it()
in our queries by calling it with the
appropriate arguments.
Make sure to execute the statement to define the function before using it in your queries.
Now you have learned how to define custom functions using the define_function!
macro, how to execute the generated statement to define the function, and how to
use the defined function in your queries. Refer to the SurrealDB documentation
for more information on custom functions and their usage.
Define Field Statement
The define_field
statement is used to define a field in SurrealDB. It allows you to specify various options and permissions for the field. This documentation provides an overview of the syntax and usage of the define_field
statement.
Table of Contents
Syntax
The basic syntax of the define_field
statement is as follows:
#![allow(unused)] fn main() { define_field(field_name) .on_table(table) .type_(field_type) .value(default_value) .assert(assertion) .permissions(permission_statements); }
field_name
: The name of the field to define.table
: The name of the table where the field belongs.field_type
: The type of the field.default_value
(optional): The default value for the field.assertion
(optional): An assertion condition for the field.permission_statements
(optional): The permissions for the field.
The define_field
statement supports the following methods:
.on_table(table)
: Specifies the table where the field belongs..type_(field_type)
: Specifies the type of the field..value(default_value)
: Specifies the default value for the field..assert(assertion)
: Specifies an assertion condition for the field..permissions(permission_statements)
: Specifies the permissions for the field.
Examples
Define Field with Full Configuration
To define a field with full configuration, including a default value, assertion condition, and permissions, you can use the following code:
#![allow(unused)] fn main() { let email = Field::new("email"); let user_table = Table::from("user"); let age = Field::new("age"); let statement = define_field(email) .on_table(user_table) .type_(String) .value("example@codebreather.com") .assert(cond(value().is_not(NONE)).and(value().like("is_email"))) .permissions(for_permission(Permission::Select).where_(age.greater_than_or_equal(18))) // Single permission .permissions(for_permission(&[Permission::Create, Permission::Update]).where_(name.is("Oyedayo"))) // Multiple permissions .permissions(&[ for_permission(&[Permission::Create, Permission::Delete]).where_(name.is("Oyedayo")), for_permission(Permission::Update).where_(age.less_than_or_equal(130)), ]); }
This will generate the following SQL statement:
DEFINE FIELD email ON TABLE user TYPE string VALUE 'example@codebreather.com' \
ASSERT ($value IS NOT NONE) AND ($value ~ 'is_email')
PERMISSIONS
FOR select
WHERE age >= 18
FOR create, update
WHERE name IS 'Oyedayo'
FOR create, delete
WHERE name IS 'Oyedayo'
FOR update
WHERE age <= 130;
In the example above, the define_field
statement defines a field named "email" on the "user" table. It specifies the field type as String
, sets a default value of 'example@codebreather.com'
, and adds an
assertion condition. It also sets different permissions for the field based on conditions.
Define Field with Simple Configuration
To define a field with a simple configuration, you can use the following code:
#![allow(unused)] fn main() { use FieldType::*; let email = Field::new("email"); let user_table = Table::from("user"); let statement = define_field(email).on_table(user_table).type_(String); }
This will generate the following SQL statement:
DEFINE FIELD email ON TABLE user TYPE string;
In the example above, the define_field
statement defines a field named "email" on the "user" table. It specifies the field type as String
without setting a default value, assertion condition, or permissions.
Field Types
The define_field
statement supports various field types in SurrealDB. The available field types are:
any
: Allows any data type supported by SurrealDB.array
: Represents a list.bool
: Represents true or false values.datetime
: Represents an ISO 8601 compliant date with time and time zone.decimal
: Represents any real number with arbitrary precision.duration
: Represents a length of time that can be added or subtracted from datetimes or other durations.float
: Represents a value stored in a 64-bit float.int
: Represents a value stored in a 64-bit integer.number
: Represents numbers without specifying the type, allowing SurrealDB to detect and store the number based on its minimal representation.object
: Represents formatted objects containing values of any supported type.string
: Represents a string value.record
: Represents a reference to another record in any table.geometry
: Represents a geometry type conforming to the GeoJSON format.
Geometry Types
The geometry
field type allows you to define geometric fields in SurrealDB. The available geometry types are:
feature
: Represents any geometric type.point
: Represents a point.line
: Represents a line.polygon
: Represents a polygon.multipoint
: Represents a multipoint.multiline
: Represents a multiline.multipolygon
: Represents a multipolygon.collection
: Represents a collection of geometry types.
Permission Types
The define_field
statement allows you to define permissions for the field using permission types. The available permission types are:
Create
: Allows creating new records with the field.Read
: Allows reading the field value from existing records.Update
: Allows updating the field value in existing records.Delete
: Allows deleting records that have the field.
These permission types can be used in the permissions
method to define the desired access control for the field.
You have now learned how to define fields using the define_field
statement, including different configuration options, field types, geometry types, and permission types. Use this statement to define fields in SurrealDB and specify their configurations and permissions.
Define Index Statement
The define_index
statement is used to define an index in SurrealDB. Indexes are used to improve the performance of queries by creating data structures that allow for efficient lookup and retrieval of data. This documentation provides an overview of the syntax and usage of the define_index
statement.
Table of Contents
Syntax
The basic syntax of the define_index
statement is as follows:
#![allow(unused)] fn main() { define_index(index_name: Index) .on_table(table: Table) .fields(arr![fields: Field]) .columns(arr![columns: Field]) .unique() }
index_name
: The name of the index to define.table
: The name of the table on which the index is defined.fields
: An array of fields to include in the index.columns
: An array of columns to include in the index.unique
: Specifies that the index should enforce uniqueness.
The define_index
statement supports the following features:
- Defining indexes with fields or columns.
- Specifying uniqueness for the index.
Examples
Define Index with Single Field
To define an index with a single field, you can use the following code:
#![allow(unused)] fn main() { let email = Field::new("email"); let query = define_index("userEmailIndex") .on_table("user") .fields(email) .unique(); }
In the example above, the define_index
statement defines an index named "userEmailIndex" on the table "user" with the "email" field. The index is marked as unique.
This will generate the following SQL statement:
DEFINE INDEX userEmailIndex ON TABLE user FIELDS email UNIQUE;
Define Index with Single Column
To define an index with a single column, you can use the following code:
#![allow(unused)] fn main() { let email = Field::new("email"); let query = define_index("userEmailIndex") .on_table("user") .columns(email) .unique(); }
In the example above, the define_index
statement defines an index named "userEmailIndex" on the table "user" with the "email" column. The index is marked as unique.
This will generate the following SQL statement:
DEFINE INDEX userEmailIndex ON TABLE user COLUMNS email UNIQUE;
Define Index with Multiple Fields
To define an index with multiple fields, you can use the following code:
#![allow(unused)] fn main() { let age = Field::new("age"); let name = Field::new("name"); let email = Field::new("email"); let dob = Field::new("dob"); let query = define_index("alien_index") .on_table("alien") .fields(arr![age, name, email, dob]) .unique(); }
In the example above, the define_index
statement defines an index named "alien_index" on the table "alien" with the "age", "name", "email", and "dob" fields. The index is marked as unique.
This will generate the following SQL statement:
DEFINE INDEX alien_index ON TABLE alien FIELDS age, name, email, dob UNIQUE;
Define Index with Multiple Columns
To define an index with multiple columns, you can use the
following code:
#![allow(unused)] fn main() { let age = Field::new("age"); let name = Field::new("name"); let email = Field::new("email"); let dob = Field::new("dob"); let query = define_index("alien_index") .on_table("alien") .columns(arr![age, name, email, dob]) .unique(); }
In the example above, the define_index
statement defines an index named "alien_index" on the table "alien" with the "age", "name", "email", and "dob" columns. The index is marked as unique.
This will generate the following SQL statement:
DEFINE INDEX alien_index ON TABLE alien COLUMNS age, name, email, dob UNIQUE;
You have now learned how to define indexes using the define_index
statement. Indexes improve query performance by creating data structures that allow for efficient lookup and retrieval of data. Use indexes strategically to optimize the performance of your database queries.
Define Param Statement
The define_param
statement is used to define a parameter in SurrealDB.
Parameters provide a way to store and reuse values within queries. This
documentation provides an overview of the syntax and usage of the define_param
statement.
Table of Contents
Syntax
The basic syntax of the define_param
statement is as follows:
#![allow(unused)] fn main() { define_param(param_name: Param) { // Parameter definition } }
param_name
: The name of the parameter to define.
The define_param
statement supports the following features:
- Assigning a value to the parameter.
Examples
Define Param Statement Usage
To define a parameter with a specific value, you can use the following code:
#![allow(unused)] fn main() { // Define the parameter fn endpoint_base() -> Param { Param::new("endpoint_base") } // Define the param definition itself. This must be run against the database first to use the param. let statement = define_param(endpoint_base()).value("https://dummyjson.com"); }
In the example above, the define_param
statement defines a parameter named "endpoint_base" with a value of "https://dummyjson.com".
Before using the defined parameter, it is important to run the define_param
statement to register the parameter with the database. You can do this by calling the run
method on the statement object, passing the SurrealDB instance as an argument:
#![allow(unused)] fn main() { statement.run(db); }
Once the define_param
statement has been executed, you can use the defined parameter ($endpoint_base
) across your codebase in queries and other operations.
You can then reference the parameter name in your queries to utilize the stored value.
#![allow(unused)] fn main() { let query = select(All).from(User::table()).where_(endpoint_base().equal_to("https://dummyjson.com")); }
Now you have learned how to define a parameter using the define_param
statement.
Parameters provide a way to store and reuse values within queries. Remember to
execute the define_param
statement to register the parameter with the database
before using it in your codebase. Refer to the SurrealDB documentation for more
information on parameters and their usage.
Remove Statement
The REMOVE
statement in Surreal ORM is used to remove various elements from
the database, such as databases, events, fields, indexes, logins, scopes,
namespaces, tables, and tokens. This documentation covers the usage and examples
of the REMOVE
statement for each of these elements.
Table of Contents
- Remove Database
- Remove Event
- Remove Field
- Remove Index
- Remove Login
- Remove Scope
- Remove Namespace
- Remove Table
- Remove Token
Remove Database
The REMOVE DATABASE
statement is used to remove a database from the SurrealDB.
Here's an example:
#![allow(unused)] fn main() { assert_eq!( remove_database("oyelowo").build(), "REMOVE DATABASE oyelowo;" ); }
The generated SQL query for this code block would be REMOVE DATABASE oyelowo;
.
Remove Event
The REMOVE EVENT
statement is used to remove an event from a table. Here's an
example:
#![allow(unused)] fn main() { let user = Table::new("user"); let party = Event::new("party"); let statement = remove_event(party).on_table(user); assert_eq!(statement.build(), "REMOVE EVENT party ON TABLE user;"); }
The generated SQL query for this code block would be
REMOVE EVENT party ON TABLE user;
.
Remove Field
The REMOVE FIELD
statement is used to remove a field from a table. Here's an
example:
#![allow(unused)] fn main() { let user = Table::new("user"); let name = Field::new("name"); let statement = remove_field(name).on_table(user); assert_eq!(statement.build(), "REMOVE FIELD name ON TABLE user;"); }
The generated SQL query for this code block would be
REMOVE FIELD name ON TABLE user;
.
Remove Index
The REMOVE INDEX
statement is used to remove an index from a table. Here's an
example:
#![allow(unused)] fn main() { let user = Table::new("user"); let party = TableIndex::new("party"); let statement = remove_index(party).on_table(user); assert_eq!(statement.build(), "REMOVE INDEX party ON TABLE user;"); }
The generated SQL query for this code block would be
REMOVE INDEX party ON TABLE user;
.
Remove Login
The REMOVE LOGIN
statement is used to remove a login from either a namespace
or a database. Here are examples for removing a login on a namespace and a
database:
#![allow(unused)] fn main() { let login = Login::new("login"); // Remove login on a namespace let statement = remove_login(login).on_namespace(); assert_eq!(statement.build(), "REMOVE LOGIN login ON NAMESPACE;"); // Remove login on a database let statement = remove_login(login).on_database(); assert_eq!(statement.build(), "REMOVE LOGIN login ON DATABASE;"); }
The generated SQL queries for these code blocks would be
REMOVE LOGIN login ON NAMESPACE;
and REMOVE LOGIN login ON DATABASE;
respectively.
Remove Scope
The REMOVE SCOPE
statement is used to remove a scope from the SurrealDB.
Here's an example:
#![allow(unused)] fn main() { let scope = Scope::new("scope"); let statement = remove_scope(scope); assert_eq!(statement.build(), "REMOVE SCOPE scope;"); }
The generated SQL query for this code block would be REMOVE SCOPE scope;
.
Remove Namespace
The REMOVE NAMESPACE
statement is used to
remove a namespace from the SurrealDB. Here's an example:
#![allow(unused)] fn main() { let namespace = Namespace::new("namespace"); let statement = remove_namespace(namespace); assert_eq!(statement.build(), "REMOVE NAMESPACE namespace;"); }
The generated SQL query for this code block would be
REMOVE NAMESPACE namespace;
.
Remove Table
The REMOVE TABLE
statement is used to remove a table from the SurrealDB.
Here's an example:
#![allow(unused)] fn main() { let table = Table::new("table"); let statement = remove_table(table); assert_eq!(statement.build(), "REMOVE TABLE table;"); }
The generated SQL query for this code block would be REMOVE TABLE table;
.
Remove Token
The REMOVE TOKEN
statement is used to remove a token from either a namespace
or a database. Here are examples for removing a token on a namespace and a
database:
#![allow(unused)] fn main() { let token = Token::new("token"); // Remove token on a namespace let statement = remove_token(token).on_namespace(); assert_eq!(statement.build(), "REMOVE TOKEN token ON NAMESPACE;"); // Remove token on a database let statement = remove_token(token).on_database(); assert_eq!(statement.build(), "REMOVE TOKEN token ON DATABASE;"); }
The generated SQL queries for these code blocks would be
REMOVE TOKEN token ON NAMESPACE;
and REMOVE TOKEN token ON DATABASE;
respectively.
That concludes the documentation for the REMOVE
statement in Surreal ORM. Use
the examples and explanations provided to effectively remove various elements
from the database.
Info Statement
The INFO
statement in Surreal ORM is used to retrieve information about
various elements in the database, such as key-value pairs, namespaces,
databases, scopes, and tables. This documentation covers the usage and examples
of the INFO
statement for each of these elements.
Table of Contents
- Info for Key-Value (KV) Pairs
- Info for Namespaces
- Info for Databases
- Info for Scopes
- Info for Tables
Info for Key-Value (KV) Pairs
The INFO FOR KV
statement is used to retrieve information about key-value
pairs in the SurrealDB. Here's an example:
#![allow(unused)] fn main() { let statement = info_for().kv().build(); assert_eq!(statement, "INFO FOR KV;"); }
The generated SQL query for this code block would be INFO FOR KV;
.
Info for Namespaces
The INFO FOR NS
statement is used to retrieve information about namespaces in
the SurrealDB. Here's an example:
#![allow(unused)] fn main() { let statement = info_for().namespace().build(); assert_eq!(statement, "INFO FOR NS;"); }
The generated SQL query for this code block would be INFO FOR NS;
.
Info for Databases
The INFO FOR DB
statement is used to retrieve information about databases in
the SurrealDB. Here's an example:
#![allow(unused)] fn main() { let statement = info_for().database().build(); assert_eq!(statement, "INFO FOR DB;"); }
The generated SQL query for this code block would be INFO FOR DB;
.
Info for Scopes
The INFO FOR SCOPE
statement is used to retrieve information about a specific
scope in the SurrealDB. Here's an example:
#![allow(unused)] fn main() { let statement = info_for().scope("test_scope").build(); assert_eq!(statement, "INFO FOR SCOPE test_scope;"); }
The generated SQL query for this code block would be
INFO FOR SCOPE test_scope;
.
Info for Tables
The INFO FOR TABLE
statement is used to retrieve information about a specific
table in the SurrealDB. Here's an example:
#![allow(unused)] fn main() { let statement = info_for().table("test_table").build(); assert_eq!(statement, "INFO FOR TABLE test_table;"); }
The generated SQL query for this code block would be
INFO FOR TABLE test_table;
.
That concludes the documentation for the INFO
statement in Surreal ORM. Use
the examples and explanations provided to retrieve information about key-value
pairs, namespaces, databases, scopes, and tables effectively.
Sleep Statement
The SLEEP
statement in Surreal ORM is used to introduce a delay or pause in
the execution of a program or query. It allows you to control the timing of your
operations by specifying a duration to wait before proceeding further. This
documentation covers the usage and examples of the SLEEP
statement.
Table of Contents
Sleep Statement Usage
The SLEEP
statement is used to introduce a pause in the program or query
execution. It takes a duration parameter to specify the length of the pause.
Here's an example:
#![allow(unused)] fn main() { use std::time::Duration; let statement = sleep(Duration::from_secs(43)); }
In the code snippet above, we create a Duration
object with a duration of 43
seconds and pass it to the sleep
function to create the SLEEP
statement.
You can use the SLEEP
statement to introduce delays or pauses in your program
or query execution to control the timing of your operations effectively.
That concludes the documentation for the SLEEP
statement in Surreal ORM. Use
the provided examples and explanations to introduce pauses in your program or
query execution as needed.
Operators
SQL Query Builder: Field Operators
All these operators can also be chained together to create more complex conditions. For instance:
#![allow(unused)] fn main() { age.outside(18, 65).and(age.not_equal(99)); p1.outside(18, 65).and(p1.not_equal(99)); }
The chaining of these operators is quite flexible and allows for the construction of complex query logic in a clear and concise manner.
Table of Contents
- Introduction
- Comparison Operators
- String Operators
- Relational Operators
- Arithmetic Operators
- Logical Operators
- Membership Operators
- Conclusion
Introduction
This document provides an overview of the different SQL query field operators available in our SQL Query Builder. For each operator, a brief description, usage, and examples are provided.
Comparison Operators
equal
, eq
The equal
operator checks if the given field is equal to the provided value. It returns true if the condition is met, and false otherwise.
The eq
operator is an alias for equal
.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let field = Field::new("field"); field.equal(5); field.eq(9); ## `not_equal`, `neq` The `not_equal` operator checks if the given field is not equal to the provided value. It returns true if the condition is met, and false otherwise. The `neq` operator is an alias for `not_equal`. Usage: ```rust use surreal_orm::*; let ref price = Field::new("price"); price.not_equal(100); price.neq(100); }
exactly_equal
The exactly_equal
operator checks if the given field is exactly equal to the provided value. It is generally used for fields that are binary or have specific precision requirements.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let name = Field::new("name"); let p1 = Field::new("p1"); name.exactly_equal("Oyelowo"); p1.exactly_equal(3.14); }
any_equal
The any_equal
operator checks if any value in a list of values is equal to the given field.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let status = Field::new("status"); let p1 = Field::new("p1"); status.any_equal(vec!["ACTIVE", "IN PROGRESS"]); p1.any_equal(vec!["APPLE", "BANANA"]); }
all_equal
The all_equal
operator checks if all values in a list are equal to the given field. It's a niche operator and has limited use.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let id = Field::new("id"); let p1 = Field::new("p1"); id.all_equal([1, 3, 5]); p1.all_equal([2, 2, 2]); }
like
The like
operator checks if the given field matches the provided pattern. %
is used as a wildcard character.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let name = Field::new("name"); let p1 = Field::new("p1"); name.like("Jo"); p1.like("son"); }
not_like
The not_like
operator checks if the given field does not match the provided pattern. %
is used as a wildcard character.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let name = Field::new("name"); let p1 = Field::new("p1"); name.not_like("Jo%"); p1.not_like("%son"); }
any_like
The any_like
operator checks if any value in a list of values matches the given field.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let status = Field::new("status"); let p1 = Field::new("p1"); status.any_like(vec!["ACTIVE", "IN PROGRESS"]); p1.any_like(vec!["APPLE", "BANANA"]); }
all_like
The all_like
operator checks if all values in a list match the given field. It's a niche operator and has limited use.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let id = Field::new("id"); let p1 = Field::new("p1"); id.all_like(vec!["1", "2", "4"]); p1.all_like(vec!["2", "2", "2"]); }
less_than
, lt
The less_than
operator checks if the given field is less than the provided value.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let age = Field::new("age"); let p1 = Field::new("p1"); age.less_than(18); p1.lt(100); }
less_than_or_equal
, lte
The less_than_or_equal
operator checks if the given field is less than or equal to the provided value.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let age = Field::new("age"); let p1 = Field::new("p1"); age.less_than_or_equal(18); p1.lte(100); }
greater_than
, gt
The greater_than
operator checks if the given field is greater than the provided value.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let age = Field::new("age"); let p1 = Field::new("p1"); age.greater_than(18); p1.gt(100); }
greater_than_or_equal
, gte
The greater_than_or_equal
operator checks if the given field is greater than or equal to the provided value.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let age = Field::new("age"); let p1 = Field::new("p1"); age.greater_than_or_equal(18); p1.gte(100); }
add, plus, +
The add
operator adds a value to the given field.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let salary = Field::new("salary"); let age = Field::new("age"); let p1 = Field::new("p1"); salary.add(500); age + 500; p1.plus(200); }
subtract, minus, -
The subtract
operator subtracts a value from the given field.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let salary = Field::new("salary"); let age = Field::new("age"); let p1 = Field::new("p1"); salary.subtract(500); age - 500; p1.subtract(200); }
multiply, mul, *
The multiply
operator multiplies the given field by the provided value.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let salary = Field::new("salary"); let quantity = Field::new("quantity"); let p1 = Field::new("p1"); quantity.multiply(price); salary * 434; p1.multiply(10); }
This will generate the following SQL statement:
quantity * price
p1 * 10
divide, div, /
The divide
operator divides the given field by the provided value.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let salary = Field::new("salary"); let quantity = Field::new("quantity"); let count = Field::new("count"); let param = Param::new("param"); salary.divide(343); quantity / count; param.div(2); }
power
, pow
The power
operator raises the given field to the power of the provided value.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let length = Field::new("length"); let p1 = Field::new("p1"); length.power(2); p1.power(3); }
trthy_and
The truthy_and
operator performs a logical AND operation between the field and the provided value.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let is_active = Field::new("is_active"); is_active.truthy_and(true); }
truthy_or
The truthy_or
operator performs a logical OR operation between the field and the provided value.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let is_active = Field::new("is_active"); is_active.truthy_or(is_paid); }
This will generate the following SQL statement:
is_active OR is_paid
p1 OR p2
and
The and
operator is used to combine multiple conditions in a WHERE clause to create more complex conditions. It returns true if all conditions are true.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let price = Field::new("price"); price.and(54).and(92); }
or
The or
operator is used to combine multiple conditions in a WHERE clause to create more complex conditions. It returns true if at least one of the conditions is true.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let = Field::new("price"); is_active.or(is_paid); p1.or(p2); }
This will generate the following SQL statement:
is
The is
operator compares if a field is equal to a specific value.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let age = Field::new("age"); let p1 = Field::new("p1"); age.is(21); p1.is("John"); }
is_not
The is_not
operator compares if a field is not equal to a specific value.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let age = Field::new("age"); age.is_not(21); p1.is_not("John"); }
contains
The contains
operator checks if a field contains a specific value.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let names = Field::new("names"); names.contains("John"); }
contains_not
The contains_not
operator checks if a field does not contain a specific value.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let names = Field::new("names"); names.contains_not("John"); }
contains_all
The contains_all
operator checks if a field contains all specified values.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let tags = Field::new("tags"); tags.contains_all(vec!["novel", "adventure"]); }
contains_any
The contains_any
operator checks if a field contains any of the specified values.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let tags = Field::new("tags"); tags.contains_any(vec!["novel", "adventure"]); }
contains_none
The contains_none
operator checks if a field does not contain any of the specified values.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let tags = Field::new("tags"); tags.contains_none(vec!["novel", "adventure"]); }
inside
and in_
The inside
and in_
operators check if a field's value is within a specified array of values.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let scores = Field::new("scores"); let p1 = Field::new("p1"); scores.inside(vec![20, 30, 40]); p1.inside(vec![20, 30, 40]); }
not_inside
The not_inside
operator checks if a field's value is not within a specified array of values.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let scores = Field::new("scores"); scores.not_inside(vec![20, 30, 40]); }
all_inside
The all_inside
operator checks if all values in a field are within a specified array of values.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let tags = Field::new("tags"); tags.all_inside(["novel", "adventure", "mystery"]); }
any_inside
The any_inside
operator checks if any value in a field is within a specified array of values.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let tags = Field::new("tags"); tags.any_inside(vec!["novel", "adventure", "mystery"]); }
none_inside
The none_inside
operator checks if none of the values in a field are within a specified array of values.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let tags = Field::new("tags"); tags.none_inside(["novel", "adventure", "mystery"]); }
outside
The outside
operator checks whether a geometry value is outside another geometry value.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let point = Field::new("point"); let area = Param::new("area"); point.outside(area); }
intersects
The intersects
operator checks whether a geometry value intersects annother geometry value.
Usage:
#![allow(unused)] fn main() { use surreal_orm::*; let area1 = Field::new("area1"); let area2 = Field::new("area2"); area1.intersects(area2); }
Also, note the distinction between Field
and Param
in the usage and examples.
A Field
represents a column in a database table, while a Param
represents a parameter
that could be used in the for value assignment. These are interchangeable in the context of these operators,
meaning that you can apply the same operators whether you are comparing fields or parameters.
Conclusion
This document covers the complete list of SQL Query Builder field operators. Using these operators will help you build complex and robust SQL queries. Always ensure that you use the correct operator for your specific needs to prevent unexpected results or errors.
Parameters
Parameters in SurrealDB serve as essential tools for storing and manipulating data within queries. The ORM simplifies this process, making it intuitive and streamlined.
Table of Contents
Query Creation and Execution
The ORM abstracts away much of the complexity involved in crafting queries. To calculate the average strength of weapons, for instance:
#![allow(unused)] fn main() { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let ref weapon = Weapon::table(); let weapon::Schema { ref strength, .. } = Weapon::schema(); let weapon_stats::Schema { averageStrength, .. } = WeaponStats::schema(); let generated_weapons = (0..=14) .map(|i| Weapon { name: format!("weapon_{}", i), strength: i, ..Default::default() }) .collect::<Vec<_>>(); insert(generated_weapons).return_many(db.clone()).await?; let created_stats_statement = create::<WeaponStats>().set(averageStrength.equal_to(block! { LET strengths = select_value(strength).from(weapon); LET total = math::sum!(strengths); LET count = count!(strengths); LET distance = 65; RETURN math::ceil!((((total / count) * (count * total)) / (total + 4)) * 100); })); assert_eq!( created_stats_statement.to_raw().build(), "CREATE weapon_stats SET averageStrength = {\n\ LET $strengths = (SELECT VALUE strength FROM weapon);\n\n\ LET $total = math::sum($strengths);\n\n\ LET $count = count($strengths);\n\n\ RETURN math::ceil(((($total / $count) * ($count * $total)) / ($total + 4)) * 100);\n\ };" ); assert_eq!( created_stats_statement.fine_tune_params(), "CREATE weapon_stats SET averageStrength = {\n\ LET $strengths = $_param_00000001;\n\n\ LET $total = math::sum($strengths);\n\n\ LET $count = count($strengths);\n\n\ RETURN math::ceil(((($total / $count) * ($count * $total)) / ($total + $_param_00000002)) * $_param_00000003);\n\ };" ); }
This block of code demonstrates the ORM's ability to define and utilize parameters within queries.
Native ORM Parameters
SurrealDB provides a set of predefined variables designed to simplify query
development. While these predefined parameters can be utilized directly within
your queries, it's crucial to note that you cannot declare new parameters with
these specific names. The ORM is equipped with built-in functions that represent
these standard SurrealDB parameters. A function like after()
corresponds to
the $after
parameter in raw queries. These functions allow developers to
interact with the database at a high level, abstracting away the complexity of
raw queries.
To bridge this system with the ORM, these predefined variables are represented by functions in the ORM, each mimicking the name of the corresponding parameter:
Here's a list of some of the prominent parameters and their descriptions:
Function | Parameter | Description |
---|---|---|
auth() | $auth | Represents the currently authenticated scope user. |
token() | $token | Represents values held inside the JWT token used for the current session. |
session() | $session | Values from session functions as an object. |
before() | $before | Value before a field mutation. |
after() | $after | Value post field mutation. |
value() | $value | Post mutation value (identical to $after for events). |
input() | $input | Initially inputted value in a field definition; the value clause might have modified the $value variable. |
parent() | $parent | Parent record in a subquery. |
event() | $event | Type of table event triggered on an event. |
These native functions simplify the query-writing process, enabling developers to focus on the logic of their application without getting bogged down by the intricacies of the database language.
Advanced Parameter Name Creation
For those requiring further customization, the create_param_name_fn!()
macro
is available. This macro not only aids in generating custom parameter names but
also supports field traversal using parameter paths. Typically though, you will
use this with the define_param
statement when you want to define a constant
global variable. However, in a typical let statement
(e.g used within the
block!
macro), this is automatically handled.
Suppose you want to create a custom parameter name for a user's age. Using the macro:
#![allow(unused)] fn main() { create_param_name_fn!(user_age); }
If you would like to add a rust doc comment, you can do so as shown below:
#![allow(unused)] fn main() { create_param_name_fn!( /// $user_age represents the age of a user => userAge ); }
To use the param name created above, you can invoke it as user_age
This means that any parameter name created with this macro can be used for field traversal. For more information on field traversal, refer to the Field Traversal chapter.
Transaction Management in Surreal ORM
Surreal ORM provides transaction management capabilities to ensure the integrity and consistency of database operations. This allows you to group multiple database operations into a single atomic unit that can be committed or canceled as a whole. This documentation covers the Begin Transaction, Commit Statement, and Cancel Transaction features in Surreal ORM.
Table of Contents
- Begin Transaction
- Commit Statement
- Cancel Transaction
- Handling Transactions with Database Operations
Begin Transaction
The begin_transaction
statement in Surreal ORM marks the beginning of a
transaction. It sets the context for a series of database operations that should
be treated as a single atomic unit. By starting a transaction, you can ensure
the integrity and consistency of your database operations.
To begin a transaction, you can use the begin_transaction
statement. Let's see
an example:
#![allow(unused)] fn main() { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); begin_transaction() ... // Perform database operations within the transaction ; // or block!{ BEGIN TRANSACTION; ... // Perform database operations within the transaction } Ok(()) }
In the code snippet above, the begin_transaction
statement is used to start a
transaction. This sets the context for the subsequent database operations.
Commit Statement
The commit
statement in Surreal ORM is used to commit a transaction and save
the changes made within the transaction. It ensures that the changes are durable
and permanent in the database.
Recommended Approaches
Using block!
Macro with Commit Statement also Within Block for Chaining Multiple Statements
To perform a transaction and commit the changes, you can use the block!
macro
to chain multiple statements together. The commit_transaction
statement is
used within the block!
macro to explicitly indicate the commitment of the
transaction. Let's take a look at an example:
#![allow(unused)] fn main() { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let ref id1 = Account::create_id("one".into()); let ref id2 = Account::create_id("two".into()); let acc = Account::schema(); let amount_to_transfer = 300.00; block! { BEGIN TRANSACTION; LET acc1 = create().content(Account { id: id1.clone(), balance: 135_605.16, }); LET acc2 = create().content(Account { id: id2.clone(), balance: 91_031.31, }); LET updated1 = update::<Account>(id1).set(acc.balance.increment_by(amount_to_transfer)); LET update2 = update::<Account>(id2).set(acc.balance.decrement_by(amount_to_transfer)); COMMIT TRANSACTION; }; Ok(()) }
In the code snippet above, the block!
macro is used to define a transaction
with multiple statements. The LET
statement is used to bind variables `
acc1,
acc2,
updated1, and
update2to the respective statements. The
BEGIN
TRANSACTIONstatement marks the start of the transaction, and the
COMMIT
TRANSACTION` statement explicitly commits the transaction.
Using the block!
macro with the commit_transaction
statement within the
block provides a clear and concise way to define a transaction and commit the
changes.
Using begin_transaction function with block!
Macro for Chaining Multiple Statements
Another recommended approach is to use the block!
macro to chain multiple
statements together within a transaction. The commit_transaction
statement is
called separately after the block!
macro to explicitly commit the transaction.
Let's see an example:
#![allow(unused)] fn main() { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let ref id1 = Account::create_id("one".into()); let ref id2 = Account::create_id("two".into()); let acc = Account::schema(); let amount_to_transfer = 300.00; let transaction_query = begin_transaction() .query(block! { LET acc1 = create().content(Account { id: id1.clone(), balance: 135_605.16, }); LET acc2 = create().content(Account { id: id2.clone(), balance: 91_031.31, }); LET updated1 = update::<Account>(id1).set(acc.balance.increment_by(amount_to_transfer)); LET update2 = update::<Account>(id2).set(acc.balance.decrement_by(amount_to_transfer)); }) .commit_transaction(); transaction_query.run(db.clone()).await?; Ok(()) }
In this approach, the block!
macro is used to define a transaction block that
includes multiple statements. The BEGIN TRANSACTION
and COMMIT TRANSACTION
statements mark the start and end of the transaction, respectively. The LET
statement is used to bind variables to the statements within the block.
Using the block!
macro for chaining multiple statements and explicitly
committing the transaction provides a more structured and organized way to
handle complex transactions.
Less Recommended Approach
The less recommended approach involves chaining multiple statements directly
without using the block!
macro. Although functional, this approach may feel
less ergonomic, especially when there is a need to bind and share variables
within the statements.
Chaining Multiple Statements Directly
Here's an example of chaining multiple statements directly without using the
block!
macro:
#![allow(unused)] fn main() { #[tokio::test] async fn test_transaction_commit_increment_and_decrement_update() -> SurrealOrmResult<()> { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let ref id1 = Account::create_id("one".into()); let ref id2 = Account::create_id("two".into()); let amount_to_transfer = 300.00; let acc = Account::schema(); begin_transaction() .query(create().content(Account { id: id1.clone(), balance: 135_605.16, })) .query(create().content(Account { id: id2.clone(), balance: 91_031.31, })) .query(update::<Account>(id1).set(acc.balance.increment_by(amount_to_transfer))) .query(update::<Account>(id2).set(acc.balance.decrement_by(amount_to_transfer))) .commit_transaction() .run(db.clone()) .await?; // Assertions and other code... Ok(()) } }
In this approach, multiple statements are chained directly within the
transaction. The create
and update
statements
are used to perform operations on the Account
table.
The less recommended approach of chaining multiple statements directly can be less ergonomic, especially when dealing with complex transactions that require variable bindings and subqueries.
It is generally recommended to use the recommended approaches with the block!
macro for better readability, automation of variable bindings, and subquery
handling.
Cancel Transaction
The cancel
transaction feature in Surreal ORM allows you to roll back a
transaction and discard the changes made within the transaction. It is useful
when you want to undo a series of database operations within a transaction.
To cancel a transaction, you can use the cancel_transaction
statement. Let's
see an example:
#![allow(unused)] fn main() { let db = Surreal::new::<Mem>(()).await.unwrap(); db.use_ns("test").use_db("test").await.unwrap(); let transaction_query = begin_transaction() .query(create().content(Account { id: Account::create_id("one".into()), balance: 135_605.16, })) .cancel_transaction(); transaction_query.run(db.clone()).await?; Ok(()) }
In the code snippet above, the cancel_transaction
statement is used to cancel
the ongoing transaction. This ensures that any changes made within the
transaction are discarded, and the database state remains unchanged.
Handling Transactions with Database Operations
When performing database operations within a transaction, it is important to ensure that the operations are executed as a single atomic unit. Surreal ORM provides transaction management features to facilitate this.
To handle transactions with database operations, you can follow these steps:
- Begin the transaction using the
begin_transaction
statement. - Chain the necessary database operations using the appropriate ORM statements.
- Use the recommended approaches described earlier to define and commit the transaction.
- If needed, use the
cancel_transaction
statement to cancel the transaction and discard any changes.
By following these steps, you can ensure the integrity and consistency of your database operations and handle transactions effectively.
That concludes the documentation for the Begin Transaction, Commit Statement, and Cancel Transaction features in Surreal ORM. Use the recommended approaches to perform transactions, commit changes, handle cancellations, and manage your database operations effectively.
Functions
Overview
Array Functions
Surreal ORM provides a set of array functions that allow you to manipulate and perform operations on arrays. These functions are designed to work with arrays of various types, including vectors, fields, and parameters. This documentation covers the usage and examples of the array functions available in Surreal ORM.
Table of Contents
Append
The append
function appends a value to the end of an array.
#![allow(unused)] fn main() { use surreal_orm::{functions::array, *}; let result = array::append!(vec![1, 2, 3, 4, 5], 6); }
You can use the append
function to add values to an existing array.
Combine
The combine
function combines all values from two arrays together, returning
an array of arrays.
#![allow(unused)] fn main() { use surreal_orm::{functions::array, *}; let result = array::combine!(vec![1, 2, 3], vec![4, 5, 6]); }
The combine
function provides the same functionality as the append
function
but can work with two arrays instead of appending a single value.
Concat
The concat
function merges two arrays together, returning an array that may
contain duplicate values.
#![allow(unused)] fn main() { use surreal_orm::{functions::array, *}; let result = array::concat!(vec![1, 2, 3], vec![4, 5, 6]); }
The concat
function provides the same functionality as the combine
function
but does not remove duplicate values from the resulting array.
Union
The union
function combines two arrays together, removing duplicate values,
and returning a single array.
#![allow(unused)] fn main() { use surreal_orm::{functions::array, *}; let result = array::union!(vec![1, 2, 3], vec![4, 5, 6]); }
The union
function provides the same functionality as the concat
function
but removes duplicate values from the resulting array.
Difference
The difference
function determines the difference between two arrays,
returning a single array containing items that are not in both arrays.
#![allow(unused)] fn main() { use surreal_orm::{functions::array, *}; let result = array::difference!(vec![1, 2, 3], vec![4, 5, 6]); }
The difference
function provides the same functionality
as the previous functions but returns only the unique values that are present in one array but not in the other.
Intersect
The intersect
function calculates the values that intersect two arrays,
returning a single array containing the values present in both arrays.
#![allow(unused)] fn main() { use surreal_orm::{functions::array, *}; let result = array::intersect!(vec![1, 2, 3], vec![4, 5, 6]); }
The intersect
function provides the same functionality as the previous
functions but returns only the values that are common between the two arrays.
Complement
The complement
function returns the complement of two arrays, returning a
single array containing items that are not in the second array.
#![allow(unused)] fn main() { use surreal_orm::{functions::array, *}; let result = array::complement!(vec![1, 2, 3, 4], vec![3, 4, 5, 6]); }
The complement
function provides the same functionality as the previous
functions but returns only the values that are present in the first array but
not in the second array.
Distinct
The distinct
function calculates the unique values in an array, returning a
single array.
#![allow(unused)] fn main() { use surreal_orm::{functions::array, *}; let result = array::distinct!(vec![1, 2, 3]); }
You can use the distinct
function to obtain unique values from an array.
Flatten
The flatten
function flattens an array of arrays, returning a new array with
all sub-array elements concatenated into it.
#![allow(unused)] fn main() { use surreal_orm::{functions::array, *}; let result = array::flatten!(array![vec![1, 2], vec![3, 4], "SurrealDB", vec![5, 6]]); }
The flatten
function provides the same functionality as the previous functions
but flattens an array of arrays.
Group
The group
function flattens and returns the unique items in an array.
#![allow(unused)] fn main() { use surreal_orm::{functions::array, *}; let result = array::group!(array![1, 2, 3, 4, array![3, 5, 6], vec![2, 4, 5, 6], 7, 8, 8, 9]); }
The group
function provides the same functionality as the previous functions
but returns only the unique items in the array.
Insert
The insert
function inserts a value into an array at a specific position.
#![allow(unused)] fn main() { use surreal_orm::{functions::array, *}; let result = array::insert!(vec![1, 2, 3, 4], 5, 2); }
The insert
function allows you to insert a value into an array at a specified
index.
Len
The len
function calculates the length of an array, returning a number. This
function includes all items when counting the number of items in the array.
#![allow(unused)] fn main() { use surreal_orm::{functions::array, *}; let result = array::len!(vec![1, 2, 3]); }
You can use the len
function to calculate the length of an array.
Pop
The pop
function removes a value from the end of an array and returns it.
#![allow(unused)] fn main() { use surreal_orm::{functions::array, *}; let result = array::pop!(vec![1, 2, 3, 4]); }
You can use the pop
function to remove the last value from an array.
Prepend
The prepend
function prepends a value to the end of an array.
#![allow(unused)] fn main() { use surreal_orm::{functions::array, *}; let result = array::prepend!(vec![1, 2, 3, 4], 5); }
You can use the prepend
function to add a value to the beginning of an array.
Push
The push
function appends a value to the end of an array.
#![allow(unused)] fn main() { use surreal_orm::{functions::array, *}; let result = array::push!(vec![1, 2, 3, 4], 5); }
The push
function provides the same functionality as the prepend
function
but appends the value to the end of the array instead of the beginning.
Remove
The remove
function removes an item from a specific position in an array.
#![allow(unused)] fn main() { use surreal_orm::{functions::array, *}; let result = array::remove!(vec![1, 2, 3, 4, 5], 2); }
You can use the remove
function to delete an item from a specific position in
an array.
Reverse
The reverse
function reverses the order of the elements in an array.
#![allow(unused)] fn main() { use surreal_orm::{functions::array, *}; let result = array::reverse!(vec![1, 2, 3, 4, 5]); }
You can use the reverse
function to reverse the order of elements in an array.
Sort
The sort
function sorts an array in ascending or descending order.
#![allow(unused)] fn main() { use surreal_orm::{functions::array, *}; let result = array::sort!(vec![3, 1, 2], "asc"); }
You can use the sort
function to sort an array in ascending or descending
order.
The sort
function also provides the following ordering options:
"asc"
: Sorts the array in ascending order."desc"
: Sorts the array in descending order.false
: Does not sort the array.
You can use the sort
function with different ordering options to sort an array
accordingly.
Asc and Desc
The asc
and desc
functions are shorthand convenience functions for the
sort
function. They sort values in an array in ascending or descending order,
respectively.
#![allow(unused)] fn main() { use surreal_orm::{functions::array, *}; let result_asc = array::sort::asc!(vec![3, 1, 2]); let result_desc = array::sort::desc!(vec![3, 1, 2]); }
The asc
and desc
functions provide the same functionality as the sort
function but with a more concise syntax.
These are the array functions available in Surreal ORM. Use them to perform various operations on arrays and manipulate array data effectively.
That concludes the documentation for the array functions in Surreal ORM. Refer to this documentation whenever you need to use array functions in your code.
Count Function
This chapter introduces the count
macros provided by Surreal ORM. The count
macros are used to generate SQL queries for counting records in a database
table.
Table of Contents
- count!()
- count!().__as__(alias)
- count!(field)
- count!(field.operation(value))
- count!(condition1.and(condition2))
- count!(array)
count!()
The count!()
macro counts all records in a table. It generates the SQL query
count()
.
#![allow(unused)] fn main() { use surreal_orm::{count, *}; let result = count!(); }
Generated SQL query:
count()
The count!()
macro provides the following functionality:
to_raw().build()
: Converts thecount
macro into a raw SQL query string. In this case, it would be"count()"
.
count!().__as__(alias)
The count!().__as__(alias)
macro allows you to specify an alias for the count
result. It generates the SQL query count() AS alias
.
#![allow(unused)] fn main() { use surreal_orm::{count, AliasName}; let head_count = AliasName::new("head_count"); let result = count!().__as__(head_count); }
Generated SQL query:
count() AS head_count
The count!().__as__(alias)
macro provides the same functionality as
count!()
, but with an additional AS
clause to specify the alias for the
count result.
count!(field)
The count!(field)
macro counts records in a table based on a specific field.
It generates the SQL query count(field)
.
#![allow(unused)] fn main() { use surreal_orm::{count, Field}; let email = Field::new("email"); let result = count!(email); }
Generated SQL query:
count(email)
The count!(field)
macro provides the same functionality as count!()
, but
with a specific field to count records on.
count!(field.operation(value))
The count!(field.operation(value))
macro allows you to perform filter
operations on the count. It generates the SQL query
count(field.operation(value))
.
#![allow(unused)] fn main() { use surreal_orm::{count, Field}; let email = Field::new("email"); let result = count!(email.greater_than(15)); }
Generated SQL query:
count(email > 15)
The count!(field.operation(value))
macro provides the same functionality as
count!(field)
, but with a filter operation applied to the field.
count!(condition1.and(condition2))
The count!(condition1.and(condition2))
macro allows you to apply multiple
conditions to the count. It generates the SQL query
count(condition1 AND condition2)
.
#![allow(unused)] fn main() { use surreal_orm::{count, Field, cond}; let email = Field::new("email"); let age = Field::new("age"); let result = count!(cond(age.greater_than(15)).and(email.like("oyelowo@example.com"))); }
Generated SQL query:
count((age > 15) AND (email ~ 'oyelowo@example.com'))
The `count!(condition1.and(condition2))
macro provides the same functionality as
count!(field.operation(value)), but with multiple conditions combined using the
AND`
operator.
count!(array)
The count!(array)
macro counts the number of elements in an array. It
generates the SQL query count(array)
.
#![allow(unused)] fn main() { use surreal_orm::{count, array}; let result = count!(array![1, 2, 3, 4, 5]); }
Generated SQL query:
count([1, 2, 3, 4, 5])
The count!(array)
macro provides the same functionality as count!()
, but
with an array as the input for counting.
Crypto Functions
This chapter introduces the crypto macros provided by Surreal ORM. The crypto macros are used for cryptographic operations such as password hashing and comparison.
Table of Contents
- argon2::compare!()
- argon2::generate!()
- pbkdf2::compare!()
- pbkdf2::generate!()
- scrypt::compare!()
- scrypt::generate!()
- bcrypt::compare!()
- bcrypt::generate!()
argon2::compare!()
The argon2::compare!()
macro compares two values using the Argon2 hashing
algorithm. It has the following syntax:
#![allow(unused)] fn main() { let result = argon2::compare!("Oyelowo", "Oyedayo"); }
The argon2::compare!()
macro generates the following SQL query:
crypto::argon2::compare('Oyelowo', 'Oyedayo')
argon2::generate!()
The argon2::generate!()
macro generates a hash value using the Argon2 hashing
algorithm. It has the following syntax:
#![allow(unused)] fn main() { let result = argon2::generate!("Oyelowo"); }
The argon2::generate!()
macro generates the following SQL query:
crypto::argon2::generate('Oyelowo')
pbkdf2::compare!()
The pbkdf2::compare!()
macro compares two values using the PBKDF2 hashing
algorithm. It has the following syntax:
#![allow(unused)] fn main() { let result = pbkdf2::compare!("hash_value", "password"); }
The pbkdf2::compare!()
macro generates the following SQL query:
crypto::pbkdf2::compare('hash_value', 'password')
pbkdf2::generate!()
The pbkdf2::generate!()
macro generates a hash value using the PBKDF2 hashing
algorithm. It has the following syntax:
#![allow(unused)] fn main() { let result = pbkdf2::generate!("password"); }
The pbkdf2::generate!()
macro generates the following SQL query:
crypto::pbkdf2::generate('password')
scrypt::compare!()
The scrypt::compare!()
macro compares two values using the scrypt hashing
algorithm. It has the following syntax:
#![allow(unused)] fn main() { let result = scrypt::compare!("hash_value", "password"); }
The scrypt::compare!()
macro generates the following SQL query:
crypto::scrypt::compare('hash_value', 'password')
scrypt::generate!()
The scrypt::generate!()
macro generates a hash value using the scrypt hashing
algorithm. It has the
following syntax:
#![allow(unused)] fn main() { let result = scrypt::generate!("password"); }
The scrypt::generate!()
macro generates the following SQL query:
crypto::scrypt::generate('password')
bcrypt::compare!()
The bcrypt::compare!()
macro compares two values using the bcrypt hashing
algorithm. It has the following syntax:
#![allow(unused)] fn main() { let result = bcrypt::compare!("hash_value", "password"); }
The bcrypt::compare!()
macro generates the following SQL query:
crypto::bcrypt::compare('hash_value', 'password')
bcrypt::generate!()
The bcrypt::generate!()
macro generates a hash value using the bcrypt hashing
algorithm. It has the following syntax:
#![allow(unused)] fn main() { let result = bcrypt::generate!("password"); }
The bcrypt::generate!()
macro generates the following SQL query:
Geo Functions
This chapter introduces the geo macros provided by the Surreal ORM. The geo macros are used for geospatial operations such as calculating area, distance, bearing, centroid, and encoding/decoding hashes.
Table of Contents
- geo::area!()
- geo::bearing!()
- geo::centroid!()
- geo::distance!()
- geo::hash::decode!()
- geo::hash::encode!()
geo::area!()
The geo::area!()
macro calculates the area of a polygon. It has the following
syntax:
#![allow(unused)] fn main() { let poly = polygon!( exterior: [ (x: -111., y: 45.), (x: -111., y: 41.), (x: -104., y: 41.), (x: -104., y: 45.), ], interiors: [ [ (x: -110., y: 44.), (x: -110., y: 42.), (x: -105., y: 42.), (x: -105., y: 44.), ], ], ); let result = geo::area!(poly); }
The geo::area!()
macro generates the following SQL query:
geo::area({ type: 'Polygon', coordinates: [[[-111, 45], [-111, 41], [-104, 41], [-104, 45], [-111, 45]], [[[-110, 44], [-110, 42], [-105, 42], [-105, 44], [-110, 44]]]] })
geo::bearing!()
The geo::bearing!()
macro calculates the bearing between two points. It has
the following syntax:
#![allow(unused)] fn main() { let point1 = point! { x: 40.02f64, y: 116.34, }; let point2 = point! { x: 80.02f64, y: 103.19, }; let result = geo::bearing!(point1, point2); }
The geo::bearing!()
macro generates the following SQL query:
geo::bearing((40.02, 116.34), (80.02, 103.19))
geo::centroid!()
The geo::centroid!()
macro calculates the centroid of a polygon. It has the
following syntax:
#![allow(unused)] fn main() { let poly = polygon!( exterior: [ (x: -111., y: 45.), (x: -111., y: 41.), (x: -104., y: 41.), (x: -104., y: 45.), ], interiors: [ [ (x: -110., y: 44.), (x: -110., y: 42.), (x: -105., y: 42.), (x: -105., y: 44.), ], ], ); let result = geo::centroid!(poly); }
The geo::centroid!()
macro generates the following SQL query:
geo::centroid({ type: 'Polygon', coordinates: [[[-111, 45], [-111, 41], [-104, 41], [-104, 45], [-111, 45]], [[[-110, 44], [-110, 42], [-105, 42], [-105, 44], [-110, 44]]]] })
geo::distance!()
The geo::distance!()
macro calculates the distance between two points. It has
the following syntax:
#![allow(unused)] fn main() { let point1 = point! { x: 40.02f64, y: 116.34, }; let point2 = point! { x: 80.02f64, y: 103.19, }; let result = geo::distance!(point1, point2); }
The geo::distance!()
macro generates the following SQL query:
geo::distance((40.02, 116.34), (80.02, 103.19))
geo::hash::decode!()
The geo::hash::decode!()
macro decodes a geohash string. It has the following
syntax:
#![allow(unused)] fn main() { let result = geo::hash::decode!("mpuxk4s24f51"); }
The geo::hash::decode!()
macro generates the following SQL query:
geo::hash::decode('mpuxk4s24f51')
geo::hash::encode!()
The geo::hash::encode!()
macro encodes a point or polygon into a geohash
string. It has the following syntax:
#![allow(unused)] fn main() { let point = point! { x: 40.02f64, y: 116.34, }; let result = geo::hash::encode!(point, 5); }
The geo::hash::encode!()
macro generates the following SQL query:
geo::hash::encode((40.02, 116.34), 5)
HTTP Functions
This chapter introduces the http macros provided by the Surreal ORM. The http macros are used for performing remote HTTP requests such as HEAD, GET, POST, PUT, and PATCH.
Table of Contents
http::head!()
The http::head!()
macro performs a remote HTTP HEAD request. It has the
following syntax:
#![allow(unused)] fn main() { http::head!("https://codebreather.com"); }
The http::head!()
macro generates the following function call:
http::head("https://codebreather.com", None as Option<ObjectLike>)
http::get!()
The http::get!()
macro performs a remote HTTP GET request. It has the
following syntax:
#![allow(unused)] fn main() { http::get!("https://codebreather.com"); }
The http::get!()
macro generates the following function call:
http::get("https://codebreather.com", None as Option<ObjectLike>)
http::delete!()
The http::delete!()
macro performs a remote HTTP DELETE request. It has the
following syntax:
#![allow(unused)] fn main() { http::delete!("https://codebreather.com"); }
The http::delete!()
macro generates the following function call:
http::delete("https://codebreather.com", None as Option<ObjectLike>)
http::post!()
The http::post!()
macro performs a remote HTTP POST request. It has the
following syntax:
#![allow(unused)] fn main() { http::post!("https://codebreather.com", body); }
The http::post!()
macro generates the following function call:
http::post("https://codebreather.com", body, None as Option<ObjectLike>)
http::put!()
The http::put!()
macro performs a remote HTTP PUT request. It has the
following syntax:
#![allow(unused)] fn main() { http::put!("https://codebreather.com", body); }
The http::put!()
macro generates the following function call:
http::put("https://codebreather.com", body, None as Option<ObjectLike>)
http::patch!()
The http::patch!()
macro performs a remote HTTP PATCH request. It has the
following syntax:
#![allow(unused)] fn main() { http::patch!("https://codebreather.com", body); }
The http::patch!()
macro generates the following function call:
http::patch("https://codebreather.com", body, None as Option<ObjectLike>)
Validation Functions
This documentation provides an overview of the validation functions in the codebase. The is
macros are used to create validation functions for checking various conditions on values.
Table of Contents
Alphanum
The is::alphanum
function checks whether a value has only alphanumeric characters. It is also aliased as is_alphanum!
.
Arguments
value
- The value to check. It could be a field or a parameter that represents the value.
Example
#![allow(unused)] fn main() { use surreal_orm::{*, functions::is, statements::let_}; let result = is::alphanum!("oyelowo1234"); assert_eq!(result.to_raw().build(), "is::alphanum('oyelowo1234')"); let alphanum_field = Field::new("alphanum_field"); let result = is::alphanum!(alphanum_field); assert_eq!(result.to_raw().build(), "is::alphanum(alphanum_field)"); block!{ LET alphanum_param = "oyelowo1234"; LET result = is::alphanum!(alphanum_param); }; }
Alpha
The is::alpha
function checks whether a value has only alpha characters. It is also aliased as is_alpha!
.
Arguments
value
- The value to check. It could be a field or a parameter that represents the value.
Example
#![allow(unused)] fn main() { use surreal_orm::{*, functions::is, statements::let_}; let result = is::alpha!("oyelowo"); assert_eq!(result.to_raw().build(), "is::alpha('oyelowo')"); let alpha_field = Field::new("alpha_field"); let result = is::alpha!(alpha_field); assert_eq!(result.to_raw().build(), "is::alpha(alpha_field)"); }
ASCII
The is::ascii
function checks whether a value has only ASCII characters. It is also aliased as is_ascii!
.
Arguments
value
- The value to check. It could be a field or a parameter that represents the value.
Example
#![allow(unused)] fn main() { use surreal_orm::{*, functions::is, statements::let_}; let result = is::ascii!("oyelowo"); assert_eq!(result.to_raw().build(), "is::ascii('oyelowo')"); let ascii_field = Field::new("ascii_field"); let result = is::ascii!(ascii_field); assert_eq!(result.to_raw().build(), "is::ascii(ascii_field)"); }
Domain
The is::domain
function checks whether a value is a domain. It is also aliased as is_domain!
.
Arguments
value
- The value to check. It could be a field or a parameter that represents the value.
Example
#![allow(unused)] fn main() { use surreal_orm::{*, functions::is, statements::let_}; let result = is::domain!("oyelowo.com"); assert_eq!(result.to_raw().build(), "is::domain('oyelowo.com')"); let domain_field = Field::new("domain_field"); let result = is::domain!(domain_field); assert_eq!(result.to_raw().build(), "is::domain(domain_field)"); }
The is::email
function checks whether a value is an email. It is also aliased as is_email!
.
Arguments
value
- The value to check. It could be a field or a parameter that represents the value.
Example
#![allow(unused)] fn main() { use surreal_orm::{*, functions::is, statements::let_}; let result = is::email!("oyelowo@codebreather.com"); assert_eq!(result.to_raw().to_string(), "is::email('oyelowo@codebreather.com')"); let email_field = Field::new("email_field"); let result = is::email!(email_field); assert_eq!(result.to_raw().to_string(), "is::email(email_field)"); }
Hexadecimal
The is::hexadecimal
function checks whether a value is hexadecimal. It is also aliased as is_hexadecimal!
.
Arguments
value
- The value to check. It could be a field or a parameter that represents the value.
Example
#![allow(unused)] fn main() { use surreal_orm::{*, functions::is, statements::let_}; let result = is::hexadecimal!("oyelowo"); assert_eq!(result.to_raw().to_string(), "is::hexadecimal('oyelowo')"); let hexadecimal_field = Field::new("hexadecimal_field"); let result = is::hexadecimal!(hexadecimal_field); assert_eq!(result.to_raw().to_string(), "is::hexadecimal(hexadecimal_field)"); let!(hexadecimal_param = "oyelowo"); let result = is::hexadecimal!(hexadecimal_param); assert_eq!(result.fine_tune_params(), "is::hexadecimal($hexadecimal_param)"); }
Latitude
The is::latitude
function checks whether a value is a latitude value. It is also aliased as is_latitude!
.
Arguments
value
- The value to check. It could be a field or a parameter that represents the value.
Example
#![allow(unused)] fn main() { use surreal_orm::{*, functions::is, statements::let_}; let result = is::latitude!("-0.118092"); assert_eq!(result.to_raw().build(), "is::latitude('-0.118092')"); let latitude_field = Field::new("latitude_field"); let result = is::latitude!(latitude_field); assert_eq!( result.to_raw().build(), "is::latitude(latitude_field)"); }
Longitude
The is::longitude
function checks whether a value is a longitude value. It is also aliased as is_longitude!
.
Arguments
value
- The value to check. It could be a field or a parameter that represents the value.
Example
#![allow(unused)] fn main() { use surreal_orm::{*, functions::is, statements::let_}; let result = is::longitude!("51.509865"); assert_eq!(result.to_raw().build(), "is::longitude('51.509865')"); let longitude_field = Field::new("longitude_field"); let result = is::longitude!(longitude_field); assert_eq!(result.to_raw().build(), "is::longitude(longitude_field)"); }
Numeric
The is::numeric
function checks whether a value has only numeric characters. It is also aliased as is_numeric!
.
Arguments
value
- The value to check. It could be a field or a parameter that represents the value.
Example
#![allow(unused)] fn main() { use surreal_orm::{*, functions::is, statements::let_}; let result = is::numeric!("oyelowo"); assert_eq!(result.to_raw().build(), "is::numeric('oyelowo')"); let numeric_field = Field::new("numeric_field"); let result = is::numeric!(numeric_field); assert_eq!(result.to_raw().build(), "is::numeric(numeric_field)"); }
Semver
The is::semver
function checks whether a value matches a semver version. It is also aliased as is_semver!
.
Arguments
value
- The value to check. It could be a field or a parameter that represents the value.
Example
#![allow(unused)] fn main() { use surreal_orm::{*, functions::is, statements::let_}; let result = is::semver!("oyelowo"); assert_eq!(result.to_raw().build(), "is::semver('oyelowo')"); let semver_field = Field::new("semver_field"); let result = is::semver!(semver_field); assert_eq!(result.to_raw().build(), "is::semver(semver_field)"); }
UUID
The is::uuid
function checks whether a value is a UUID. It is also aliased as is_uuid!
.
Arguments
value
- The value to check. It could be a field or a parameter that represents the value.
Example
#![allow(unused)] fn main() { use surreal_orm::{*, functions::is, statements::let_}; let result = is::uuid!("oyelowo"); assert_eq!(result.to_raw().build(), " is::uuid('oyelowo')"); let uuid_field = Field::new("uuid_field"); let result = is::uuid!(uuid_field); assert_eq!(result.to_raw().build(), "is::uuid(uuid_field)"); }
Datetime
The is::datetime
function checks whether a value matches a datetime format. It is also aliased as is_datetime!
.
Arguments
value
- The value to check. It could be a field or a parameter that represents the value.
Example
#![allow(unused)] fn main() { use surreal_orm::{*, functions::is, statements::let_}; let result = is::datetime!("oyelowo"); assert_eq!(result.to_raw().build(), "is::datetime('oyelowo')"); let datetime_field = Field::new("datetime_field"); let result = is::datetime!(datetime_field); assert_eq!(result.to_raw().build(), "is::datetime(datetime_field)"); }
Math Functions
Table of Contents
- math::abs!()
- math::ceil!()
- math::floor!()
- math::round!()
- math::sqrt!()
- math::mean!()
- math::median!()
- math::mode!()
- math::min!()
- math::product!()
- math::sum!()
math::abs!()
The math::abs function returns the absolute value of a number.
Function signature: math::abs(number) -> number
Example:
#![allow(unused)] fn main() { math::abs!(45.23); }
math::ceil!()
The math::ceil function rounds a number up to the next largest integer.
Function signature: math::ceil(number) -> number
Example:
#![allow(unused)] fn main() { math::ceil!(45.23); }
math::floor!()
The math::floor function rounds a number down to the next largest integer.
Function signature: math::floor(number) -> number
Example:
#![allow(unused)] fn main() { math::floor!(45.23); }
math::round!()
The math::round function rounds a number up or down to the nearest integer.
Function signature: math::round(number) -> number
Example:
#![allow(unused)] fn main() { math::round!(45.23); }
math::sqrt!()
The math::sqrt function returns the square root of a number.
Function signature: math::sqrt(number) -> number
Example:
#![allow(unused)] fn main() { math::sqrt!(45.23); }
math::mean!()
The math::mean function returns the average of a set of numbers.
Function signature: math::mean(array) -> number
Example:
#![allow(unused)] fn main() { math::mean!(vec![1, 2, 3, 4, 5]); }
math::median!()
The math::median function returns the median of a set of numbers.
Function signature: math::median(array) -> number
Example:
#![allow(unused)] fn main() { math::median!(vec![1, 2, 3, 4, 5]); }
math::mode!()
The math::mode function returns the mode of a set of numbers.
Function signature: math::mode(array) -> number
Example:
#![allow(unused)] fn main() { math::mode!(vec![1, 2, 3, 4, 5]); }
math::min!()
The math::min function returns the minimum number in a set of numbers.
Function signature: math::min(array) -> number
Example:
#![allow(unused)] fn main() { math::min!(vec![1, 2, 3, 4, 5]); }
math::product!()
The math::product function returns the product of a set of numbers.
Function signature: math::product(array) -> number
Example:
#![allow(unused)] fn main() { math::product!(vec![1, 2, 3, 4, 5]); }
math::sum!()
The math::sum function returns the total sum of a set of numbers.
Function signature: math::sum(array) -> number
Example:
#![allow(unused)] fn main() { math::sum!(vec![1, 2, 3, 4, 5]); }
That concludes the documentation for the math macros.
Meta functions
Parse Functions
Table of Contents
- parse::email::host()
- parse::email::user()
- parse::url::domain()
- parse::url::fragment()
- parse::url::host()
- parse::url::path()
- parse::url::port()
- parse::url::query()
parse::email::host()
The parse::email::host
function parses and returns the email host from a valid email address. This function is also aliased as parse_email_host!
.
Function signature: parse::email::host(string) -> value
Example:
#![allow(unused)] fn main() { parse::email::host!("oyelowo@codebreather.com"); }
parse::email::user()
The parse::email::user
function parses and returns the email username from a valid email address. This function is also aliased as parse_email_user!
.
Function signature: parse::email::user(string) -> value
Example:
#![allow(unused)] fn main() { parse::email::user!("oyelowo@codebreather.com"); }
parse::url::domain()
The parse::url::domain
function parses and returns the domain from a valid URL. This function is also aliased as parse_url_domain!
.
Function signature: parse::url::domain(string) -> value
Example:
#![allow(unused)] fn main() { parse::url::domain!("https://codebreather.com:443/topics?arg=value#fragment"); }
parse::url::fragment()
The parse::url::fragment
function parses and returns the fragment from a valid URL. This function is also aliased as parse_url_fragment!
.
Function signature: parse::url::fragment(string) -> value
Example:
#![allow(unused)] fn main() { parse::url::fragment!("https://codebreather.com:443/topics?arg=value#fragment"); }
parse::url::host()
The parse::url::host
function parses and returns the hostname from a valid URL. This function is also aliased as parse_url_host!
.
Function signature: parse::url::host(string) -> value
Example:
#![allow(unused)] fn main() { parse::url::host!("https://codebreather.com:443/topics?arg=value#fragment"); }
parse::url::path()
The parse::url::path
function parses and returns the path from a valid URL. This function is also aliased as parse_url_path!
.
Function signature: parse::url::path(string) -> value
Example:
#![allow(unused)] fn main() { parse::url::path!("https://codebreather.com:443/topics?arg=value#fragment"); }
parse::url::port()
The parse::url::port
function parses and returns the port from a valid URL. This function is also aliased as parse_url_port!
.
Function signature: `parse::url
::port(string) -> value`
Example:
#![allow(unused)] fn main() { parse::url::port!("https://codebreather.com:443/topics?arg=value#fragment"); }
parse::url::query()
The parse::url::query
function parses and returns the query from a valid URL. This function is also aliased as parse_url_query!
.
Function signature: parse::url::query(string) -> value
Example:
#![allow(unused)] fn main() { parse::url::query!("https://codebreather.com:443/topics?arg=value#fragment"); }
That concludes the documentation for the parse macros.
Rand Functions
Table of Contents
- rand!()
- rand::bool!()
- rand::uuid!()
- rand::uuid::v4!()
- rand::uuid::v7!()
- rand::enum!()
- rand::string!()
- rand::guid!()
- rand::float!()
- rand::int!()
- rand::time!()
rand!()
The rand!()
macro generates a random number.
Example:
#![allow(unused)] fn main() { use surreal_orm::functions::rand; let result = rand!(); }
rand::bool!()
The rand::bool!()
macro generates a random boolean value.
Example:
#![allow(unused)] fn main() { use surreal_orm::functions::rand; let result = rand::bool!(); }
rand::uuid!()
The rand::uuid!()
macro generates a random UUID.
Example:
#![allow(unused)] fn main() { use surreal_orm::functions::rand; let result = rand::uuid!(); }
rand::uuid::v4!()
The rand::uuid::v4!()
macro generates a random UUID v4.
Example:
#![allow(unused)] fn main() { use surreal_orm::functions::rand; let result = rand::uuid::v4!(); }
rand::uuid::v7!()
The rand::uuid::v7!()
macro generates a random UUID v7.
Example:
#![allow(unused)] fn main() { use surreal_orm::functions::rand; let result = rand::uuid::v7!(); }
rand::enum!()
The rand::enum!()
macro generates a random value from a list of options.
Example:
#![allow(unused)] fn main() { use surreal_orm::functions::rand; use surreal_orm::functions::rand::arr; let result = rand::enum!(arr!["one", "two", 3, 4.15385, "five", true]); }
rand::string!()
The rand::string!()
macro generates a random string.
Example:
#![allow(unused)] fn main() { use surreal_orm::functions::rand; let result = rand::string!(); }
rand::guid!()
The rand::guid!()
macro generates a random GUID.
Example:
#![allow(unused)] fn main() { use surreal_orm::functions::rand; let result = rand::guid!(); }
rand::float!()
The rand::float!()
macro generates a random floating-point number.
Example:
#![allow(unused)] fn main() { use surreal_orm::functions::rand; let result = rand::float!(); }
rand::int!()
The rand::int!()
macro generates a random integer.
Example:
#![allow(unused)] fn main() { use surreal_orm::functions::rand; let result = rand::int!(); }
rand::time!()
The rand::time!()
macro generates a random time value.
Example:
#![allow(unused)] fn main() { use surreal_orm::functions::rand; let result = rand::time!(); }
Session functions
Table of Contents
session::db!()
The session::db!()
macro returns the currently selected database.
Example:
#![allow(unused)] fn main() { use surreal_orm::functions::session; session::db!(); }
session::id!()
The session::id!()
macro returns the current user's session ID.
Example:
#![allow(unused)] fn main() { use surreal_orm::functions::session; session::id!(); }
session::ip!()
The session::ip!()
macro returns the current user's session IP address.
Example:
#![allow(unused)] fn main() { use surreal_orm::functions::session; session::ip!(); }
session::ns!()
The session::ns!()
macro returns the currently selected namespace.
Example:
#![allow(unused)] fn main() { use surreal_orm::functions::session; session::ns!(); }
session::origin!()
The session::origin!()
macro returns the current user's HTTP origin.
Example:
#![allow(unused)] fn main() { use surreal_orm::functions::session; session::origin!(); }
session::sc!()
The session::sc!()
macro returns the current user's authentication scope.
Example:
#![allow(unused)] fn main() { use surreal_orm::functions::session; session::sc!(); }
Sleep function
Table of Contents
sleep!()
The sleep!()
macro suspends the current thread for the specified duration.
Example:
#![allow(unused)] fn main() { use std::time; use surreal_orm::functions::sleep; let result = sleep!(time::Duration::from_secs(55)); assert_eq!(result.to_raw().build(), "sleep(55s)"); }
String functions
Table of Contents
- string::concat!()
- string::join!()
- string::ends_with!()
- string::starts_with!()
- string::split!()
- string::len!()
- string::reverse!()
- string::trim!()
- string::slug!()
- string::lowercase!()
- string::uppercase!()
- string::words!()
- string::repeat!()
- string::replace!()
- string::slice!()
string::concat!()
The string::concat!()
macro allows you to concatenate multiple values into a string.
Examples:
#![allow(unused)] fn main() { use crate::functions::string; use crate::*; let title = Field::new("title"); let result = string::concat!(title, "one", 3, 4.15385, " ", true); assert_eq!(result.fine_tune_params(), "string::concat(title, $_param_00000001, $_param_00000002, $_param_00000003, $_param_00000004, $_param_00000005)"); assert_eq!( result.to_raw().build(), "string::concat(title, 'one', 3, 4.15385, ' ', true)" ); let result = string::concat!(arr!["one", "two", 3, 4.15385, "five", true]); assert_eq!(result.fine_tune_params(), "string::concat($_param_00000001, $_param_00000002, $_param_00000003, $_param_00000004, $_param_00000005, $_param_00000006)"); assert_eq!( result.to_raw().build(), "string::concat('one', 'two', 3, 4.15385, 'five', true)" ); }
string::join!()
The string::join!()
macro allows you to join multiple values into a string using a delimiter.
Examples:
#![allow(unused)] fn main() { use crate::functions::string; use crate::*; let title = Field::new("title"); let result = string::join!(title, "one", 3, 4.15385, " ", true); assert_eq!(result.fine_tune_params(), "string::join(title, $_param_00000001, $_param_00000002, $_param_00000003, $_param_00000004, $_param_00000005)"); assert_eq!( result.to_raw().build(), "string::join(title, 'one', 3, 4.15385, ' ', true)" ); let result = string::join!(arr!["one", "two", 3, 4.15385, "five", true]); assert_eq!(result.fine_tune_params(), "string::join($_param_00000001, $_param_00000002, $_param_00000003, $_param_00000004 , $_param_00000005, $_param_00000006)"); assert_eq!( result.to_raw().build(), "string::join('one', 'two', 3, 4.15385, 'five', true)" ); }
string::ends_with!()
The string::ends_with!()
macro allows you to check if a string ends with a specified substring.
Examples:
#![allow(unused)] fn main() { use crate::functions::string; use crate::*; let name = Field::new("name"); let result = string::ends_with!(name, "lowo"); assert_eq!( result.fine_tune_params(), "string::ends_with(name, $_param_00000001)" ); assert_eq!(result.to_raw().build(), "string::ends_with(name, 'lowo')"); let result = string::ends_with!("Oyelowo", "lowo"); assert_eq!( result.fine_tune_params(), "string::ends_with($_param_00000001, $_param_00000002)" ); assert_eq!( result.to_raw().build(), "string::ends_with('Oyelowo', 'lowo')" ); }
string::starts_with!()
The string::starts_with!()
macro allows you to check if a string starts with a specified substring.
Examples:
#![allow(unused)] fn main() { use crate::functions::string; use crate::*; let name = Field::new("name"); let result = string::starts_with!(name, "lowo"); assert_eq!( result.fine_tune_params(), "string::starts_with(name, $_param_00000001)" ); assert_eq!(result.to_raw().build(), "string::starts_with(name, 'lowo')"); let result = string::starts_with!("Oyelowo", "Oye"); assert_eq!( result.fine_tune_params(), "string::starts_with($_param_00000001, $_param_00000002)" ); assert_eq!( result.to_raw().build(), "string::starts_with('Oyelowo', 'Oye')" ); }
string::split!()
The string::split!()
macro allows you to split a string into multiple substrings based on a delimiter.
Examples:
#![allow(unused)] fn main() { use crate::functions::string; use crate::*; let phrase = Field::new("phrase"); let result = string::split!(phrase, ", "); assert_eq!( result.fine_tune_params(), "string::split(phrase, $_param_00000001)" ); assert_eq!(result.to_raw().build(), "string::split(phrase, ', ')"); let result = string::split!( "With great power, comes great responsibility", ", " ); assert_eq!( result.fine_tune_params(), "string::split($_param_00000001, $_param_00000002)" ); assert_eq!( result.to_raw().build(), "string::split('With great power, comes great responsibility', ', ')" ); }
string::len!()
The string::len!()
macro allows you to get the length of a string.
Examples:
#![allow(unused)] fn main() { use crate::functions::string; use crate::*; let name = Field::new("name"); let result = string::len!(name); assert_eq!(result.fine_tune_params(), "string::length(name)"); assert_eq!(result.to_raw().build(), " string::length(name)"); let result = string::len!("toronto"); assert_eq!( result.fine_tune_params(), "string::length($_param_00000001)" ); assert_eq!(result.to_raw().build(), "string::length('toronto')"); }
string::reverse!()
The string::reverse!()
macro allows you to reverse a string.
Examples:
#![allow(unused)] fn main() { use crate::functions::string; use crate::*; let name = Field::new("name"); let result = string::reverse!(name); assert_eq!(result.fine_tune_params(), "string::reverse(name)"); assert_eq!(result.to_raw().build(), "string::reverse(name)"); let result = string::reverse!("oyelowo"); assert_eq!( result.fine_tune_params(), "string::reverse($_param_00000001)" ); assert_eq!(result.to_raw().build(), "string::reverse('oyelowo')"); }
string::trim!()
The string::trim!()
macro allows you to remove leading and trailing whitespace from a string.
Examples:
#![allow(unused)] fn main() { use crate::functions::string; use crate::*; let name = Field::new("name"); let result = string::trim!(name); assert_eq!(result.fine_tune_params(), "string::trim(name)"); assert_eq!(result.to_raw().build(), "string::trim(name)"); let result = string::trim!("oyelowo"); assert_eq!(result.fine_tune_params(), "string::trim($_param_00000001)"); assert_eq!(result.to_raw().build(), "string::trim('oyelowo')"); }
string::slug!()
The string::slug!()
macro allows you to convert a string into a slug.
Examples:
#![allow(unused)] fn main() { use crate::functions::string; use crate::*; let name = Field::new("name"); let result = string::slug!(name); assert_eq!(result.fine_tune_params(), "string::slug(name)"); assert_eq!(result.to_raw().build(), "string::slug(name)"); let result = string::slug!("Codebreather is from #Jupiter"); assert_eq!(result.fine_tune_params(), "string::slug($_param_00000001)"); assert_eq!( result.to_raw().build(), "string::slug('Codebreather is from #Jupiter')" ); }
string::lowercase!()
The string::lowercase!()
macro allows you to convert a string to lowercase.
Examples:
#![allow(unused)] fn main() { use crate::functions::string; use crate::*; let name = Field::new("name"); let result = string::lowercase!(name); assert_eq!(result.fine_tune_params(), "string::lowercase(name)"); assert_eq!(result.to_raw().build(), "string::lowercase(name)"); let result = string::lowercase!("OYELOWO"); assert_eq!( result.fine_tune_params(), "string::lowercase($_param_00000001)" ); assert_eq!(result.to_raw().build(), "string::lowercase('OYELOWO')"); }
string::uppercase!()
The string::uppercase!()
macro allows you to convert a string to uppercase.
Examples:
#![allow(unused)] fn main() { use crate::functions::string; use crate::*; let name = Field::new("name"); let result = string::uppercase!(name); assert _eq!(result.fine_tune_params(), "string::uppercase(name)"); assert_eq!(result.to_raw().build(), "string::uppercase(name)"); let result = string::uppercase!("oyelowo"); assert_eq!( result.fine_tune_params(), "string::uppercase($_param_00000001)" ); assert_eq!(result.to_raw().build(), "string::uppercase('oyelowo')"); }
string::words!()
The string::words!()
macro allows you to split a string into individual words.
Examples:
#![allow(unused)] fn main() { use crate::functions::string; use crate::*; let sentence = Field::new("sentence"); let result = string::words!(sentence); assert_eq!(result.fine_tune_params(), "string::words(sentence)"); assert_eq!(result.to_raw().build(), "string::words(sentence)"); let result = string::words!("The quick brown fox"); assert_eq!( result.fine_tune_params(), "string::words($_param_00000001)" ); assert_eq!(result.to_raw().build(), "string::words('The quick brown fox')"); }
string::repeat!()
The string::repeat!()
macro allows you to repeat a string multiple times.
Examples:
#![allow(unused)] fn main() { use crate::functions::string; use crate::*; let word = Field::new("word"); let result = string::repeat!(word, 3); assert_eq!(result.fine_tune_params(), "string::repeat(word, $_param_00000001)"); assert_eq!(result.to_raw().build(), "string::repeat(word, 3)"); let result = string::repeat!("hello", 5); assert_eq!(result.fine_tune_params(), "string::repeat($_param_00000001, $_param_00000002)"); assert_eq!(result.to_raw().build(), "string::repeat('hello', 5)"); }
string::replace!()
The string::replace!()
macro allows you to replace occurrences of a substring in a string with another substring.
Examples:
#![allow(unused)] fn main() { use crate::functions::string; use crate::*; let phrase = Field::new("phrase"); let result = string::replace!(phrase, "world", "Universe"); assert_eq!( result.fine_tune_params(), "string::replace(phrase, $_param_00000001, $_param_00000002)" ); assert_eq!( result.to_raw().build(), "string::replace(phrase, 'world', 'Universe')" ); let result = string::replace!("Hello, world!", "world", "Universe"); assert_eq!( result.fine_tune_params(), "string::replace($_param_00000001, $_param_00000002, $_param_00000003)" ); assert_eq!( result.to_raw().build(), "string::replace('Hello, world!', 'world', 'Universe')" ); }
string::slice!()
The string::slice!()
macro allows you to extract a portion of a string.
Examples:
#![allow(unused)] fn main() { use crate::functions::string; use crate::*; let phrase = Field::new("phrase"); let result = string::slice!(phrase, 6, 11); assert_eq!( result.fine_tune_params(), "string::slice(phrase, $_param_00000001, $_param_00000002)" ); assert_eq!(result.to_raw().build(), "string::slice (phrase, 6, 11)"); let result = string::slice!("Hello, world!", 7, 12); assert_eq!( result.fine_tune_params(), "string::slice($_param_00000001, $_param_00000002, $_param_00000003)" ); assert_eq!( result.to_raw().build(), "string::slice('Hello, world!', 7, 12)" ); }
string::concat!()
The string::concat!()
macro allows you to concatenate multiple strings.
Examples:
#![allow(unused)] fn main() { use crate::functions::string; use crate::*; let word1 = Field::new("word1"); let word2 = Field::new("word2"); let result = string::concat!(word1, " ", word2); assert_eq!( result.fine_tune_params(), "string::concat(word1, $_param_00000001, word2)" ); assert_eq!(result.to_raw().build(), "string::concat(word1, ' ', word2)"); let result = string::concat!("Hello", ", ", "world!"); assert_eq!( result.fine_tune_params(), "string::concat($_param_00000001, $_param_00000002, $_param_00000003)" ); assert_eq!( result.to_raw().build(), "string::concat('Hello', ', ', 'world!')" ); }
string::to_string!()
The string::to_string!()
macro allows you to convert a value to a string.
Examples:
#![allow(unused)] fn main() { use crate::functions::string; use crate::*; let number = Field::new("number"); let result = string::to_string!(number); assert_eq!( result.fine_tune_params(), "string::to_string(number)" ); assert_eq!(result.to_raw().build(), "string::to_string(number)"); let result = string::to_string!(42); assert_eq!( result.fine_tune_params(), "string::to_string($_param_00000001)" ); assert_eq!(result.to_raw().build(), "string::to_string(42)"); }
Time Functions
- time::day()
- time::floor()
- time::format()
- time::group()
- time::hour()
- time::minute()
- time::month()
- time::nano()
- time::now()
- time::round()
- time::second()
- time::timezone()
- time::unix()
- time::wday()
- time::week()
- time::yday()
- time::year()
time::day()
The time::day()
function extracts the day as a number from a datetime.
Usage:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let result = time::day(dt); assert_eq!( result.to_raw().build(), "time::day('1970-01-01T00:01:01Z')" ); let rebirth_date = Field::new("rebirth_date"); let result = time::day(rebirth_date); assert_eq!(result.to_raw().build(), "time::day(rebirth_date)"); let param = Param::new("rebirth_date"); let result = time::day(param); assert_eq!(result.to_raw().build(), "time::day($rebirth_date)"); }
Example:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let result = time::day(dt); assert_eq!( result.to_raw().build(), "time::day('1970-01-01T00:01:01Z')" ); let rebirth_date = Field::new("rebirth_date"); let result = time::day(rebirth_date); assert_eq!(result.to_raw().build(), "time::day(rebirth_date)"); let param = Param::new("rebirth_date"); let result = time::day(param); assert_eq!(result.to_raw().build(), "time::day($rebirth_date)"); }
time::floor()
The time::floor()
function rounds a datetime down by a specific duration.
Usage:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let rebirth_date = Field::new("rebirth_date"); let duration = Field::new("duration"); let result = time::floor(rebirth_date, duration); assert_eq!( result.to_raw().build(), "time::floor(rebirth_date, duration)" ); }
Example:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let rebirth_date = Field::new("rebirth_date"); let duration = Field::new("duration"); let result = time::floor(rebirth_date, duration); assert_eq!( result.to_raw(). build(), "time::floor(rebirth_date, duration)" ); }
time::format()
The time::format()
function outputs a datetime according to a specific format.
Usage:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let format_str = "'Year: 'yyyy-MM-dd"; let result = time::format(dt, format_str); assert_eq!( result.to_raw().build(), "time::format('1970-01-01T00:01:01Z', 'Year: 'yyyy-MM-dd)" ); }
Example:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let format_str = "'Year: 'yyyy-MM-dd"; let result = time::format(dt, format_str); assert_eq!( result.to_raw().build(), "time::format('1970-01-01T00:01:01Z', 'Year: 'yyyy-MM-dd)" ); }
time::group()
The time::group()
function groups a datetime by a particular time interval.
Usage:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let interval = "month"; let result = time::group(dt, interval); assert_eq!( result.to_raw().build(), "time::group('1970-01-01T00:01:01Z', 'month')" ); let rebirth_date = Field::new("rebirth_date"); let interval_field = Field::new("interval"); let result = time::group(rebirth_date, interval_field); assert_eq!( result.to_raw().build(), "time::group(rebirth_date, interval)" ); let param = Param::new("rebirth_date"); let result = time::group(param, interval); assert_eq!( result.to_raw().build(), "time::group($rebirth_date, 'month')" ); }
Example:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let interval = "month"; let result = time::group(dt, interval); assert_eq!( result.to_raw().build(), "time::group('1970-01-01T00:01:01Z', 'month')" ); let rebirth_date = Field::new("rebirth_date"); let interval_field = Field::new("interval"); let result = time::group(rebirth_date, interval_field); assert_eq!( result.to_raw().build(), "time::group(rebirth_date, interval)" ); let param = Param::new("rebirth_date"); let result = time::group(param, interval); assert_eq!( result.to_raw().build(), "time::group($rebirth_date, 'month')" ); }
time::hour()
The time::hour()
function extracts the hour as a number
from a datetime.
Usage:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let result = time::hour(dt); assert_eq!( result.to_raw().build(), "time::hour('1970-01-01T00:01:01Z')" ); let rebirth_date = Field::new("rebirth_date"); let result = time::hour(rebirth_date); assert_eq!(result.to_raw().build(), "time::hour(rebirth_date)"); let param = Param::new("rebirth_date"); let result = time::hour(param); assert_eq!(result.to_raw().build(), "time::hour($rebirth_date)"); }
Example:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let result = time::hour(dt); assert_eq!( result.to_raw().build(), "time::hour('1970-01-01T00:01:01Z')" ); let rebirth_date = Field::new("rebirth_date"); let result = time::hour(rebirth_date); assert_eq!(result.to_raw().build(), "time::hour(rebirth_date)"); let param = Param::new("rebirth_date"); let result = time::hour(param); assert_eq!(result.to_raw().build(), "time::hour($rebirth_date)"); }
time::minute()
The time::minute()
function extracts the minutes as a number from a datetime.
Usage:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let result = time::minute(dt); assert_eq!( result.to_raw().build(), "time::minute('1970-01-01T00:01:01Z')" ); let rebirth_date = Field::new("rebirth_date"); let result = time::minute(rebirth_date); assert_eq!(result.to_raw().build(), "time::minute(rebirth_date)"); let param = Param::new("rebirth_date"); let result = time::minute(param); assert_eq!(result.to_raw().build(), "time::minute($rebirth_date)"); }
Example:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let result = time::minute(dt); assert_eq!( result.to_raw().build(), "time::minute('1970-01-01T00:01:01Z')" ); let rebirth_date = Field::new("rebirth_date"); let result = time::minute(rebirth_date); assert_eq!(result.to_raw().build(), "time::minute(rebirth_date)"); let param = Param::new("rebirth_date"); let result = time::minute(param); assert_eq!(result.to_raw().build(), "time::minute($rebirth_date)"); }
time::month()
The time::month()
function extracts the month as a number from a datetime.
Usage:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let result = time::month(dt); assert_eq!( result.to_raw().build(), "time::month('1970-01-01T00:01:01Z')" ); let rebirth_date = Field::new("rebirth_date"); let result = time::month(rebirth_date); assert_eq!(result.to_raw().build(), "time::month(rebirth_date)"); let param = Param::new("rebirth_date"); let result = time::month(param); assert_eq!(result.to_raw().build(), "time::month($rebirth_date)"); }
Example:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let result = time::month(dt); assert_eq!( result.to_raw().build(), "time::month('1970-01-01T00:01:01Z')" ); let rebirth_date = Field::new("rebirth_date"); let result = time::month(rebirth_date); assert_eq!(result.to_raw().build(), "time::month(rebirth_date)"); let param = Param::new("rebirth_date"); let result = time::month(param); assert_eq!(result.to_raw().build(), "time::month($rebirth_date)"); }
time::nano()
The time::nano()
function returns the number of nanoseconds since the UNIX epoch.
Usage:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let result = time::nano(); assert_eq!(result.to_raw().build(), "time::nano()"); }
Example:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let result = time::nano(); assert_eq!(result.to_raw().build(), "time::nano()"); }
time::now()
The time::now()
function returns the current datetime.
Usage:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let result = time::now(); assert_eq!(result.to_raw().build(), "time::now()"); }
Example:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let result = time::now(); assert_eq!(result.to_raw().build(), "time::now()"); }
time::round()
The time::round()
function rounds a datetime to the nearest multiple of a specific duration.
Usage:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let rebirth_date = Field::new("rebirth_date"); let duration = Field::new("duration"); let result = time::round(rebirth_date, duration); assert_eq!( result.to_raw().build(), "time::round(rebirth_date, duration)" ); }
Example:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let rebirth_date = Field::new("rebirth_date"); let duration = Field::new("duration"); let result = time::round(rebirth_date, duration); assert_eq!( result.to_raw().build(), "time::round(rebirth_date, duration)" ); }
time::second() </a
The time::second()
function extracts the second as a number from a datetime.
Usage:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let result = time::second(dt); assert_eq!( result.to_raw().build(), "time::second('1970-01-01T00:01:01Z')" ); let rebirth_date = Field::new("rebirth_date"); let result = time::second(rebirth_date); assert_eq!(result.to_raw().build(), "time::second(rebirth_date)"); let param = Param::new("rebirth_date"); let result = time::second(param); assert_eq!(result.to_raw().build(), "time::second($rebirth_date)"); }
Example:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let result = time::second(dt); assert_eq!( result.to_raw().build(), "time::second('1970-01-01T00:01:01Z')" ); let rebirth_date = Field::new("rebirth_date"); let result = time::second(rebirth_date); assert_eq!(result.to_raw().build(), "time::second(rebirth_date)"); let param = Param::new("rebirth_date"); let result = time::second(param); assert_eq!(result.to_raw().build(), "time::second($rebirth_date)"); }
time::timezone()
The time::timezone()
function returns the current local timezone offset in hours.
Usage:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let result = time::timezone(); assert_eq!(result.to_raw().build(), "time::timezone()"); }
Example:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let result = time::timezone(); assert_eq!(result.to_raw().build(), "time::timezone()"); }
time::unix()
The time::unix()
function returns the number of seconds since the UNIX epoch.
Usage:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let result = time::unix(); assert_eq!(result.to_raw().build(), "time::unix()"); }
Example:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let result = time::unix(); assert_eq!(result.to_raw().build(), "time::unix()"); }
time::wday()
The time::wday()
function extracts the week day as a number from a datetime.
Usage:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let result = time::wday(dt); assert_eq!( result.to_raw().build(), "time::wday('1970-01-01T00:01:01Z')" ); let rebirth_date = Field::new("rebirth_date"); let result = time::wday(rebirth_date); assert_eq!(result.to_raw().build(), "time::wday(rebirth_date)"); let param = Param::new("rebirth_date"); let result = time::wday(param); assert_eq!(result.to_raw().build(), "time::wday($rebirth_date)"); }
Example:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let result = time::wday(dt); assert_eq!( result.to_raw().build(), "time::wday('1970-01-01T00:01:01Z')" ); let rebirth_date = Field::new("rebirth_date"); let result = time::wday(rebirth_date); assert_eq!(result.to_raw().build(), "time::wday(rebirth_date)"); let param = Param::new("rebirth_date"); let result = time::wday(param); assert_eq!(result.to_raw().build(), "time::wday($rebirth_date)"); }
time::week()
The time::week()
function extracts the week as a number from a datetime.
Usage:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let result = time::week(dt); assert_eq!( result.to_raw().build(), "time::week('1970-01-01T00:01:01Z')" ); let rebirth_date = Field::new("rebirth_date"); let result = time::week(rebirth_date); assert_eq!(result.to_raw().build(), "time::week(rebirth_date)"); let param = Param::new("rebirth_date"); let result = time::week(param); assert_eq!(result.to_raw().build(), "time::week($rebirth_date)"); }
Example:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let result = time::week(dt); assert_eq!( result.to_raw().build(), "time::week('1970-01-01T00:01:01Z')" ); let rebirth_date = Field::new("rebirth_date"); let result = time::week(rebirth_date); assert_eq!(result.to_raw().build(), "time::week(rebirth_date)"); let param = Param::new("rebirth_date"); let result = time::week(param); assert_eq!(result.to_raw().build(), "time::week($rebirth_date)"); }
time::yday()
The time::yday()
function extracts the yday as a number from a datetime.
Usage:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let result = time::yday(dt); assert_eq!( result.to_raw().build(), "time::yday('1970-01-01T00:01:01Z')" ); let rebirth_date = Field::new("rebirth_date"); let result = time::yday(rebirth_date); assert_eq!(result.to_raw().build(), " time::yday(rebirth_date)"); let param = Param::new("rebirth_date"); let result = time::yday(param); assert_eq!(result.to_raw().build(), "time::yday($rebirth_date)"); }
Example:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let result = time::yday(dt); assert_eq!( result.to_raw().build(), "time::yday('1970-01-01T00:01:01Z')" ); let rebirth_date = Field::new("rebirth_date"); let result = time::yday(rebirth_date); assert_eq!(result.to_raw().build(), "time::yday(rebirth_date)"); let param = Param::new("rebirth_date"); let result = time::yday(param); assert_eq!(result.to_raw().build(), "time::yday($rebirth_date)"); }
time::year()
The time::year()
function extracts the year as a number from a datetime.
Usage:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let result = time::year(dt); assert_eq!( result.to_raw().build(), "time::year('1970-01-01T00:01:01Z')" ); let rebirth_date = Field::new("rebirth_date"); let result = time::year(rebirth_date); assert_eq!(result.to_raw().build(), "time::year(rebirth_date)"); let param = Param::new("rebirth_date"); let result = time::year(param); assert_eq!(result.to_raw().build(), "time::year($rebirth_date)"); }
Example:
#![allow(unused)] fn main() { use surreal_orm::{*, functions::time}; let dt = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset( chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), chrono::Utc, ); let result = time::year(dt); assert_eq!( result.to_raw().build(), "time::year('1970-01-01T00:01:01Z')" ); let rebirth_date = Field::new("rebirth_date"); let result = time::year(rebirth_date); assert_eq!(result.to_raw().build(), "time::year(rebirth_date)"); let param = Param::new("rebirth_date"); let result = time::year(param); assert_eq!(result.to_raw().build(), "time::year($rebirth_date)"); }
Type functions
Table of Contents
- type_::bool!()
- type_::datetime!()
- type_::decimal!()
- type_::duration!()
- type_::float!()
- type_::int!()
- type_::number!()
- type_::point!()
- type_::regex!()
- type_::string!()
- type_::table!()
- type_::thing!()
type_::bool!()
The type_::bool!()
macro allows you to convert a value into a boolean.
Examples:
#![allow(unused)] fn main() { use surreal_orm::macros::type_; let result = type_::bool!(43545); assert_eq!(result.to_raw().build(), "type::bool(43545)"); let bool_field = Field::new("bool_field"); let result = type_::bool!(bool_field); assert_eq!(result.to_raw().build(), "type::bool(bool_field)"); let bool_param = Param::new("bool_param"); let result = type_::bool!(bool_param); assert_eq!(result.to_raw().build(), "type::bool($bool_param)"); }
type_::datetime!()
The type_::datetime!()
macro allows you to convert a value into a datetime.
Examples:
#![allow(unused)] fn main() { use surreal_orm::macros::type_; use chrono::DateTime; use chrono::Utc; let value = DateTime::<Utc>::from_naive_utc_and_offset(chrono::NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), Utc); let result = type_::datetime!(value); assert_eq!(result.to_raw().build(), "type::datetime('1970-01-01T00:01:01Z')"); let datetime_field = Field::new("datetime_field"); let result = type_::datetime!(datetime_field); assert_eq!(result.to_raw().build(), "type::datetime(datetime_field)"); let datetime_param = Param::new("datetime_param"); let result = type_::datetime!(datetime_param); assert_eq!(result.to_raw().build(), "type::datetime($datetime_param)"); }
type_::decimal!()
The type_::decimal!()
macro allows you to convert a value into a decimal.
Examples:
#![allow(unused)] fn main() { use surreal_orm::macros::type_; let result = type_::decimal!(1234.56); assert_eq!(result.to_raw().build(), "type::decimal(1234.56)"); let decimal_field = Field::new("decimal_field"); let result = type_::decimal!(decimal_field); assert_eq!(result.to_raw().build(), "type::decimal(decimal_field)"); let decimal_param = Param::new("decimal_param"); let result = type_::decimal!(decimal_param); assert_eq!(result.to_raw().build(), "type::decimal($decimal_param)"); }
type_::duration!()
The type_::duration!()
macro allows you to convert a value into a duration.
Examples:
#![allow(unused)] fn main() { use surreal_orm::macros::type_; use std::time::Duration; let result = type_::duration!(Duration::from_secs(24 * 60 * 60 * 7)); assert_eq!(result.to_raw().build(), "type::duration(1w)"); let duration_field = Field::new("duration_field"); let result = type_::duration!(duration_field); assert_eq!(result.to_raw().build(), "type::duration(duration_field)"); let duration_param = Param::new("duration_param"); let result = type_::duration!(duration_param); assert_eq!(result.to_raw().build(), "type::duration($duration_param)"); }
type_::float!()
The type_::float!()
macro allows you to convert a value into a floating point number.
Examples:
#![allow(unused)] fn main() { use surreal_orm::macros::type_; let result = type_::float!(43.5); assert_eq!(result.to_raw().build(), "type::float(43.5)"); let float_field = Field::new("float_field"); let result = type_::float!(float_field); assert_eq!(result.to_raw().build(), "type::float(float_field)"); let float_param = Param::new("float_param"); let result = type_::float!(float_param); assert_eq!(result.to_raw().build(), "type::float($float_param)"); }
type_::int!()
The type_::int!()
macro allows you to convert a value into an integer.
Examples:
#![allow(unused)] fn main() { use surreal_orm::macros::type_; let result = type_::int!(99); assert_eq!(result.to_raw().build(), "type::int(99)"); let int_field = Field::new("int_field"); let result = type_::int!(int_field); assert_eq!(result.to_raw().build(), "type::int(int_field)"); let int_param = Param::new("int_param"); let result = type_::int!(int_param); assert_eq!(result.to_raw().build(), "type::int($int_param)"); }
type_::number!()
The type_::number!()
macro allows you to convert a value into a number.
Examples:
#![allow(unused)] fn main() { use surreal_orm::macros::type_; let result = type_::number!(5); assert_eq!(result.to_raw().build(), "type::number(5)"); let number_field = Field::new("number_field"); let result = type_::number!(number_field); assert_eq!(result.to_raw().build(), "type::number(number_field)"); let number_param = Param::new("number_param"); let result = type_::number!(number_param); assert_eq!(result.to_raw().build(), "type::number($number_param)"); }
type_::point!()
The type_::point!()
macro allows you to convert a value into a geometry point.
Examples:
#![allow(unused)] fn main() { use surreal_orm::macros::type_; let result = type_::point!(51.509865, -0.118092); assert_eq!(result.to_raw().build(), "type::point(51.509865, -0.118092)"); let point_field = Field::new("point_field"); let result = type_::point!(point_field); assert_eq!(result.to_raw().build(), "type::point(point_field)"); let point_param = Param::new("point_param"); let result = type _::point!(point_param); assert_eq!(result.to_raw().build(), "type::point($point_param)"); }
type_::regex!()
The type_::regex!()
macro allows you to convert a value into a regular expression.
Examples:
#![allow(unused)] fn main() { use surreal_orm::macros::type_; let result = type_::regex!("/[A-Z]{3}/"); assert_eq!(result.to_raw().build(), "type::regex('/[A-Z]{3}/')"); let regex_field = Field::new("regex_field"); let result = type_::regex!(regex_field); assert_eq!(result.to_raw().build(), "type::regex(regex_field)"); let regex_param = Param::new("regex_param"); let result = type_::regex!(regex_param); assert_eq!(result.to_raw().build(), "type::regex($regex_param)"); }
type_::string!()
The type_::string!()
macro allows you to convert a value into a string.
Examples:
#![allow(unused)] fn main() { use surreal_orm::macros::type_; let result = type_::string!(5454); assert_eq!(result.to_raw().build(), "type::string(5454)"); let string_field = Field::new("string_field"); let result = type_::string!(string_field); assert_eq!(result.to_raw().build(), "type::string(string_field)"); let string_param = Param::new("string_param"); let result = type_::string!(string_param); assert_eq!(result.to_raw().build(), "type::string($string_param)"); }
type_::table!()
The type_::table!()
macro allows you to convert a value into a table definition.
Examples:
#![allow(unused)] fn main() { use surreal_orm::macros::type_; use surreal_orm::statements::let_; let result = type_::table!("user"); assert_eq!(result.to_raw().build(), "type::table(user)"); let table_field = Field::new("table_field"); let result = type_::table!(table_field); assert_eq!(result.to_raw().build(), "type::table(table_field)"); let table_param = let_("table_param").equal_to("user").get_param(); let result = type_::table!(table_param); assert_eq!(result.to_raw().build(), "type::table($table_param)"); }
type_::thing!()
The type_::thing!()
macro allows you to convert a value into a record pointer.
Examples:
#![allow(unused)] fn main() { use surreal_orm::macros::type_; use surreal_orm::Table; let user = Table::from("user"); let id = "oyelowo"; let result = type_::thing!(user, id); assert_eq!(result.to_raw().build(), "type::thing(user, 'oyelowo')"); let table = Table::new("table"); let id = Field::new("id"); let result = type_::thing!(table, id); assert_eq!(result.to_raw().build(), "type::thing(table, id)"); }
Scripting function
Table of Contents
function!()
The function!()
macro allows you to define JavaScript functions with different parameters and function bodies.
Example:
#![allow(unused)] fn main() { use surreal_orm::macros::function; use surreal_orm::statements::let_; let value = let_("value").equal_to("SurrealDB").get_param(); let words = let_("words").equal_to(vec!["awesome", "advanced", "cool"]).get_param(); let f2 = function!( (value, words), "{ return `${arguments[0]} is ${arguments[1]}`; }" ); assert_eq!( f2.build(), "function($value, $words) { return `${arguments[0]} is ${arguments[1]}`; }" ); assert_eq!( f2.to_raw().build(), "function($value, $words) { return `${arguments[0]} is ${arguments[1]}`; }" ); }
Conclusion
Throughout our exploration of Surreal ORM, we've traversed the vast landscape of database management, from the foundational concepts of Object-Relational Mapping to the intricate nuances of creating advanced and efficient database queries. The journey, while occasionally challenging, has been immensely rewarding.
We started by understanding the core tenets of ORM and how Surreal ORM aims to revolutionize the way we interact with databases. The emphasis on a more intuitive, macro-driven approach not only simplifies query building but also offers a level of flexibility previously unseen in conventional ORM tools.
Our in-depth discussions shed light on the importance of efficient query
building. With the help of the surreal_orm
query builder, we explored how to
craft complex queries that maintain readability and efficiency. The cond
function and cond!
macro was a particular highlight, providing a convenient
method to generate filters without sacrificing clarity or performance.
The challenges of nested queries and multi-level operations were not overlooked. By introducing an elegant solution in the form of recursive macros, Surreal ORM ensures that developers can handle any level of complexity with ease.
However, it's essential to remember that while tools like Surreal ORM can significantly simplify database operations, the true power lies in understanding the underlying principles. A tool is only as good as the craftsman wielding it. Your grasp on the intricacies of ORM and your ability to adapt and innovate will define the success of your projects.
As we conclude this guide, it's worth noting that the world of technology is ever-evolving. While Surreal ORM offers a robust solution for today's challenges, the future might bring new requirements. Engage with the Surreal ORM community, share your insights, learn from others, and contribute towards refining and expanding this already formidable tool.
Thank you for accompanying me on this enlightening journey through Surreal ORM. Armed with the knowledge from our discussions, you're now well-equipped to harness the full potential of this tool. Here's to building more efficient, intuitive, and scalable applications!