1 个不稳定版本
0.3.0 | 2023年3月10日 |
---|
#23 在 #sled-database
用于 reindeer
33KB
299 行
Reindeer 🦌
Reindeer 🦌 提升您的 sled
!
在 sled
、serde
和 bincode
上面的一个小型结构层
Reindeer 是一个小型嵌入式实体存储,基于 sled
构建,使用 serde
和 bincode
进行序列化,完全使用 Rust 编写。
它提供了一个方便的中间层,用于在具有最小关系模型的嵌入式数据库中存储、检索和更新结构体。
入门指南
创建一个 sled
数据库
use reindeer::Db;
let db = reindeer::open("./my-db")?;
💡 由于这是一个 sled
DB,此对象可以安全地在线程之间复制和发送。
从那里,你有两种选择
- 推导
Entity
特性 - 手动实现特性。
手动实现 Entity
特性
通过使用 derive
宏
实体需要实现来自 serde
的 Serialize
和 Deserialize
特性,这些特性在 reindeer
中被方便地重新导出
use reindeer::{Serialize,Deserialize,Entity}
#[derive(Serialize,Deserialize,Entity)]
pub struct MyStruct {
pub id : u32,
pub prop1 : String,
pub prop2 : u64,
}
如果你的结构体已经有一个 id
字段,那么它将被用作存储的键。其类型必须是整数类型、String
或 Vec<u8>
,或这些类型的元组。
数据库中存储的名称将是实体的名称,保留其原始大小写。在这种情况下,请确保它是唯一具有此名称的实体。
否则,您可以使用 entity
辅助属性来指定不同的键字段和名称
#[derive(Serialize,Deserialize,Entity)]
#[entity(name = "user", id = "email")]
pub struct User {
pub email : String,
pub prop1 : String,
pub prop2 : u64,
}
通过手动实现 Entity
特性
实体需要实现来自 serde
的 Serialize
和 Deserialize
特性,这些特性在 reindeer
中被方便地重新导出
use reindeer::{Serialize,Deserialize,Entity}
#[derive(Serialize,Deserialize)]
pub struct MyStruct {
pub id : u32,
pub prop1 : String,
pub prop2 : u64,
}
然后您需要实现 Entity
特性并实现三个方法:get_key
、set_key
和 store_name,以及定义一个关联类型,
Key
-
Key
是您实体每个实例的标识符类型(“主键”)。它必须实现AsBytes
特质。😌☝ 对于String
、u32
、i32
、u64
、i64
以及Vec<u8>
,以及任何由这些类型组成的2元素元组,它都已经实现了,所以您不需要自己实现它。 -
键代表用于在数据库中标识每个struct实例的唯一键,以检索和更新它们,其类型为
Key
-
store_name
是实体存储的名称。对于每种实体类型,它应该是唯一的(可以将其视为表名)。
use reindeer::{Entity, Serialize,Deserialize};
#[derive(Serialize,Deserialize)]
struct MyStruct { key : u32, prop1 : String }
impl Entity for MyStruct{
type Key = u32;
fn store_name() -> &'static str {
"my-struct"
}
fn get_key(&self) -> &Self::Key {
&self.key
}
fn set_key(&mut self, key : &Self::Key) {
self.key = key.clone();
}
}
在系统中注册实体
在启动应用程序时注册实体一次。
let db = reindeer::open("./my-db")?;
MyStruct::register(db)?;
💡 注册实体将使Reindeer能够处理实体条目的安全删除。如果没有注册,尝试删除未注册的实体条目将导致错误。
将实例保存到数据库
现在您可以将struct实例MyStruct
保存到数据库
let db = reindeer::open("./")?;
let instance = MyStruct {
id : 0,
prop1 : String::from("Hello"),
prop2 : 2335,
}
instance.save(&db)?;
💡 如果数据库中已存在id为0的实例,它将被覆盖!
从数据库中检索实例
let instance = MyStruct::get(0,&db)?;
检索所有实例
let instances = MyStruct::get_all(&db)?;
获取满足条件的所有实体
let instances = MyStruct::get_with_filter(|m_struct| {mstruct.prop1.len > 20},&db)?;
从数据库中删除实例
MyStruct::remove(0,&db)?;
定义关系
reindeer
有三种类型的关系
sibling
:在另一个存储中具有相同键的实体(一对一关系)父子关系
:键由其父键和一个u32
(作为一个二元组)组成的实体(高效的一对一关系)自由关系
:您可以将两个不同实体的两个实例自由地连接在一起。这可以用来实现多对多关系,但与兄弟关系和父子关系相比,在查询数据库方面效率较低。**当兄弟关系和父子关系不可用时使用**。
兄弟关系
要创建兄弟实体,您需要通过覆盖指定兄弟存储以及当我们删除其中一个时会发生什么来将实体结构体链接在一起。
兄弟存储必须共享相同的键类型(因此匹配的实体将具有相同的id)。
💡 删除行为决定了当当前实体被删除时兄弟将如何处理
DeletionBehaviour::Cascade
也会删除兄弟实体DeletionBehaviour::Error
如果存在兄弟并且不删除源元素,则引发错误DeletionBehaviour::BreakLink
只是删除实体,而不删除其兄弟
使用derive
宏
您可以使用siblings
辅助属性指定兄弟
#[derive(Serialize,Deserialize,Entity)]
#[entity(name = "user", id = "email")]
#[siblings(("user_data",Cascade),("user_data2",Cascade))]
pub struct User {
pub email : String,
pub prop1 : String,
pub prop2 : u64,
}
#[derive(Serialize,Deserialize,Entity)]
#[entity(name = "user_data", id = "email")]
#[siblings(("user",Error),("user_data2",Cascade))]
pub struct UserData {
pub email : String,
pub prop3 : String,
pub prop4 : String,
pub prop5 : i64
}
在上面的示例中,删除User
实例也会删除其兄弟UserData
实例,但删除UserData
实例会引发错误并删除两者。
手动
use reindeer::{Entity,DeletionBehaviour};
impl Entity for MyStruct1{
/* ... */
fn store_name() -> &'static str {
"my_struct_1"
}
fn get_sibling_stores() -> Vec<(&'static str,DeletionBehaviour)> {
return vec![("my_struct_2",DeletionBehaviour::Cascade)]
}
}
impl Entity for MyStruct2{
/* ... */
fn store_name() -> &'static str {
"my_struct_2"
}
fn get_sibling_stores() -> Vec<(&'static str,DeletionBehaviour)> {
return vec![("my_struct_1",DeletionBehaviour::BreakLink)]
}
}
💡 如果定义了兄弟树,实体实例可能或可能没有另一个兄弟存储的兄弟!兄弟关系默认为可选。
在上面的示例中,删除一个 MyStruct1
实例也会删除其兄弟 MyStruct2
实例,但删除 MyStruct2
实例会保留其兄弟 MyStruct1
实例。
💡 兄弟实体必须具有相同的 Key
类型。
创建兄弟实体
let m_struct_1 = MyStruct1 {
/* ... */
};
let mut m_struct_2 = MyStruct2 {
/* ... */
};
m_struct_1.save(&db)?;
m_struct_1.save_sibling(m_struct_2,&db)?;
💡 这将使用 set_key
方法更新 m_struct_2
的键为 m_struct_1
的键,因此在调用 save_child
之前您提供的初始键是什么并不重要。
⚠️ 注意,如果您在 MyStruct2
的存储中创建了一个具有与 MyStruct1
存储中实体相同键的实体,但没有使用 save_sibling
,则结果相同,这两个实体将被视为相同的兄弟。
检索兄弟实体
if let Some(sibling) = m_struct_1.get_sibling::<MyStruct2>(&db)? {
/* ... */
}
💡 注意,兄弟实体可能存在或不存在,因此使用 Option
类型。
父子关系
为了在实体之间建立父子关系,子实体必须有一个 Key
类型,它是一个包含以下内容的元组:
- 父
Key
类型 u32
💡 子实体将自动递增,并通过其父键轻松检索。
使用 derive
宏
您可以像定义兄弟存储一样定义子存储,但使用 children
辅助属性。
#[derive(Serialize,Deserialize,Entity)]
#[entity(name = "user", id = "email")]
#[children(("document",Cascade))]
pub struct User {
pub email : String,
pub prop1 : String,
pub prop2 : u64,
}
#[derive(Serialize,Deserialize,Entity)]
#[entity(name = "document")]
pub struct Document {
pub id : (String,u32),
pub prop3 : String,
pub prop4 : String,
pub prop5 : i64
}
手动实现
impl Entity for Parent{
type Key = String;
/* ... */
fn store_name() -> &'static str {
"parent"
}
fn get_child_stores() -> Vec<(&'static str)> {
return vec![("child", DeletionBehaviour::Cascade)]
}
}
impl Entity for Child{
type Key = (String, u32);
/* ... */
fn store_name() -> &'static str {
"child"
}
}
在上面的示例中,删除父实体将自动删除所有子实体(归功于 Cascade
删除行为)。
为了数据库完整性,强烈建议不要在父子关系上使用 DeletionBehaviour::BreakLink
,而应使用 Error
或 Cascade
添加子实体
let parent = Parent {
/* ... */
};
let mut child = Child {
/* ... */
}
parent.save_child(child,&db)?;
💡 这将使用 set_key
方法更新 child
的键为 parent
的键和自动递增的索引,因此在调用 save_child
之前您提供的初始键是什么并不重要。
获取子实体
let children = parent.get_children::<Child>(&db)?;
自由关系
自由关系遵循与其他关系类型相同的模式,但它们可以在任何两个实体之间自由创建。这可以实现多对多关系。
💡 创建自由关系将自动创建其相反关系,使其成为双向的。
链接两个实体
let e1 = Entity1 {
/* ... */
};
let mut e2 = Entity2 {
/* ... */
}
e1.create_relation(e2,DeletionBehaviour::Cascade, DeletionBehaviour::BreakLink,None,&db)?;
在上面的示例中,两种方向的删除行为都提供了:删除 e1
将自动删除 e2
,但删除 e2
将保留 e1
并断开它们之间的链接。
DeletionBehaviour::Error
也是一个选项。
从给定的树中获取相关实体
let related_entities = e1.get_related::<Entity2>(db)?;
要获取来自另一个树中的第一个相关实体,请使用
let related_entity = e1.get_single_related::<Entity2>(db)?;
从给定的树中获取具有特定关系名称的相关实体
在创建关系时必须提供名称。
e1.create_relation(e2,DeletionBehaviour::Cascade, DeletionBehaviour::BreakLink,Some("main"),&db)?;
let related_entities = e1.get_related_with_name::<Entity2>("secondary",db)?;
要获取来自另一个树中的第一个相关实体,请使用
let related_entity = e1.get_single_related_with_name::<Entity2>("main",db)?;
断开自由关系链接
如果需要,您可以删除实体之间现有的链接
e1.remove_relation(other,db)?;
或
e1.remove_relation_with_key::<OtherEntity>(otherKey,db)?;
死锁 🔒
当为您的关联定义 DeletionBehaviour
时,请小心 不要创建死锁。
例如,如果两个兄弟相互定义了一个 DeletionBehaviour::Error
链接,那么它们中的任何一个都无法被删除...
此外,请注意您在数据库中创建的循环。虽然您可以安全地创建关系循环,但上述相同的死锁规则同样适用,并且库直到您尝试删除某物时才会检测到这些循环。
性能
虽然兄弟和父子关系默认情况下性能良好,但自由关系性能较差,并且依赖于隐藏对象存储来工作,迫使在关系创建和实体删除时对数据库进行读写。请注意这个陷阱。
此外,定义级联关系将在删除实体时递归地遍历关系,使操作比无关系的实体更重。
自动增加实体
如果您的实体 Key
类型是 u32
,您可以使用以下方式自动增加新实体:
use reindeer::AutoIncrementEntity;
let mut new_entity = Entity {
id : 0 // if you setup id with any key, saving will update it
/* ... */
};
new_entity.save_next(db)?;
// new_entity's key is now the auto-incremente value
您实体的键将自动通过 set_key
更新,以匹配找到的最后一个条目的 ID 并增加 1。
💡 注意,AutoIncrementEntity
特性必须处于作用域内。
依赖项
~1.5–2MB
~49K SLoC