# Surrealdb-id
# Description:
Using [SurrealDB](https://surrealdb.com/) is great, but using it with Rust can be tough, I faced challenges creating a struct for representing
[RecordId](https://surrealdb.com/docs/surrealdb/surrealql/datamodel/ids) and [ Relate statement](https://surrealdb.com/docs/surrealdb/surrealql/statements/relate)
Take a look at my solution here, I want to share with you, it might help you too.
### Reducing the effort on writing SurrealQL:
Before diving in, allow me to introduce my solution for simplify the process of writing SurrealQL.
Additionally, for leveraging its full potential, I highly recommend exploring another library known as surreal-derive-plus.
You can find it here:
https://crates.io/crates/surreal-derive-plus
Here is an example when they come together:
```rust
let user: RecordId = RecordId::from(("user", "Devlog"));
let blogPost: RecordId = RecordId::from(("blogPost", "How to use surrealdb"));
let discussion = Discussion { content: "Hello I really want to know more".to_string(), created_at: Default::default() };
let relation = discussion.relate(user, blogPost)
assert_eq!(
surreal_quote!("#relate(&relation)"),
"RELATE user:Devlog -> discuss -> blogPost:⟨How to use surrealdb⟩ SET content = 'Hello I really want to know more', created_at = '1970-01-01T00:00:00Z'"
);
```
# Link:
A Link can be both `record` or `id`.
### Create a Link
#### Simplest way
```rust
let user_link: Link = Link::::from(("user", "devlog"));
```
#### From any type that impl `From for RecordId` type
```rust
struct UserId {
display_name: String
}
impl From for RecordId {
fn from(value: UserId) -> Self {
RecordId::from(("user", value.display_name.as_str()))
}
}
let user_link: Link = Link::::from(UserId {display_name: "Devlog"});
```
#### Bonus: Create link with new keyword
This approach offers greater type safety compared to using From as mentioned earlier, as it restricts users to specific parameters when creating a link.
```rust
impl NewLink for Link {
fn new(params: String) -> Link {
Link::::from(UserId { display_name: params })
}
}
// Or with multiple params
//impl NewLink for Link {
// TOOD: Impl here
//}
let user_link = Link::::new(("Devlog"));
```
### Link usage
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
struct User {
name: String
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Friend {
me: Link,
my_friend: Link
}
// Every record must be impl Into
impl Into for Friend {
fn into(self) -> RecordId {
RecordId::from(("friend", self.me.name.as_str()))
}
}
// If the id needed to be type safe, create another struct
pub struct UserId {
pub display_name: String
}
// Implement From to RecordId
// so that we can use it with Link::::from(UserId)
impl From for RecordId {
fn from(value: UserId) -> Self {
RecordId::from(("user", value.display_name.as_str()))
}
}
// Or with more constraint
// by forcing Link::User::new() can only be trigger with UserId
impl NewLink for Link {
fn new(params: UserId) -> Link {
Link::from(("user", params.display_name.as_str()))
}
}
#[tokio::test]
pub async fn test() -> surrealdb::Result<()> {
let db = Surreal::new::(()).await?;
db.use_ns("test").use_db("test").await?;
let user = User { name: "Devlog".to_string() };
db.create(Resource::RecordId(user.clone().into())).content(&user).await?;
let friend = Friend {
me: Link::::new(UserId { display_name: "Devlog".to_string() }), // `::new` will have more constrained to make sure only UserId can be passed
my_friend: Link::::from(UserId { display_name: "TienDang".to_string() }) // while `::from` will work with any E where RecordId: From
};
let friend: Option = db.query(
"SELECT * FROM (CREATE friend:Devlog set me=user:Devlog, my_friend=user:TienDang) FETCH me"
).await?.take(0)?;
let friend = friend.unwrap();
// Right here, the my_friend is only id type
// and me is object type
assert_eq!(friend.my_friend.id().id.to_string(), "TienDang".to_owned());
assert_eq!(friend.me.name, "Devlog".to_owned());
Ok(())
}
```
## Relation:
### Create Relation
#### Use IdRelation
It will use `RecordId` as the default type for `in` and `out`
```rust
let relation: IdRelation = db.query("RELATE user:Devlog -> discuss -> blog:⟨How to use surrealdb⟩").await?.take(0)?;
```
#### use LinkRelation
The relation will wrap `in` and `out` in a `Link`
```rust
let relation: LinkRelation = db.query("SELECT * FROM RELATE user:Devlog -> discuss -> blog:⟨How to use surrealdb⟩ FETCH in, out").await?.take(0)?;
relation.r#in.id().id // => Devlog
relation.r#in.id().tb // => user
relation.out.id().id // ⟨How to use surrealdb⟩
relation.out.id().tb // => blog
```
## Relation usage
For example, you are in context of wring blog post, you want to
create feature discussion for your blog
```rust
use chrono::{DateTime, Utc};
use serde_derive::{Deserialize, Serialize};
use surrealdb::engine::local::Mem;
use surrealdb::opt::{RecordId, Resource};
use surrealdb::sql::Id;
use surrealdb::Surreal;
use crate::link::Link;
use crate::relation::{IdRelation, LinkRelation};
// Entity 1
#[derive(Debug, Clone, Serialize, Deserialize)]
struct User {
name: String
}
// Entity 2
#[derive(Debug, Clone, Serialize, Deserialize)]
struct BlogPost {
title: String
}
// Relation
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Discussion {
content: String,
created_at: DateTime
}
impl Into for User {
fn into(self) -> RecordId {
("user", self.name.as_str()).into()
}
}
impl Into for Discussion {
fn into(self) -> RecordId {
("discussion", Id::Number(self.created_at.timestamp_millis())).into()
}
}
impl Into for BlogPost {
fn into(self) -> RecordId {
("blog", self.title.as_str()).into()
}
}
#[tokio::test]
pub async fn test() -> surrealdb::Result<()> {
let db = Surreal::new::(()).await?;
db.use_ns("test").use_db("test").await?;
let user = Link::Record(User { name: "Devlog".to_string() });
let blogPost = Link::Record(BlogPost { title: "How to use surrealdb".to_string() });
db.create(Resource::RecordId(user.id())).content(&user).await?;
db.create(Resource::RecordId(blogPost.id())).content(&blogPost).await?;
let relation: Option> = db.query(
"SELECT * FROM RELATE user:Devlog->discuss->blog:⟨How to use surrealdb⟩ SET content='Hello I really want to know more', created_at='2020-01-01T00:00:00Z'"
).await?.take(0)?;
let relation = relation.unwrap();
assert_eq!(relation.r#in.as_ref().unwrap().id().id.to_string(), "Devlog".to_owned());
assert_eq!(relation.r#in.as_ref().unwrap().id().tb.as_str(), "user");
assert_eq!(relation.out.as_ref().unwrap().id().id.to_string(), "⟨How to use surrealdb⟩".to_owned());
assert_eq!(reation.out.as_ref().unwrap().id().tb.as_str(), "blog");
let relation: Option> = db.query(
"SELECT * FROM (RELATE user:Devlog->discuss->blog:⟨How to use surrealdb⟩ SET content='Hello I really want to know more', created_at='2020-01-01T00:00:00Z') FETCH in, out"
).await?.take(0)?;
let relation = relation.unwrap();
assert_eq!(relation.r#in.as_ref().unwrap().name.to_string(), "Devlog".to_owned());
assert_eq!(relation.out.as_ref().unwrap().title.to_string(), "How to use surrealdb".to_owned());
Ok(())
}
```