19个版本
0.3.0 | 2023年3月10日 |
---|---|
0.2.7 | 2022年9月8日 |
0.2.3 | 2022年8月16日 |
0.1.11 | 2022年8月6日 |
0.1.4 | 2022年6月21日 |
#732 在 数据库接口
每月27次下载
105KB
2K SLoC
驯鹿 🦌
驯鹿 🦌 提升你的 sled
!
在 sled
、serde
和 bincode
之上的一个小型结构层
驯鹿是一个小型嵌入式实体存储库,建立在 sled
之上,使用 serde
和 bincode
进行序列化,完全使用 Rust 编写。
它作为存储、检索和更新嵌入式数据库中结构体的便捷中间层。
入门
创建一个 sled
数据库
use reindeer::Db;
let db = reindeer::open("./my-db")?;
💡 由于这是一个 sled
数据库,这个对象可以安全地在线程之间复制和发送。
从这里,你有两种选择
- 派生
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>
以及这些类型的任意两个元素的元组,它已经实现,因此您不需要自行实现。 -
键代表在数据库中标识每个结构实例的唯一键,用于检索和更新它们,它的类型是
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能够处理实体条目的安全删除。如果没有注册,尝试删除未注册的实体条目将导致错误。
将实例保存到数据库
现在您可以将您的结构实例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
特质需要处于作用域内。
依赖项
~2.9–4MB
~68K SLoC