8 个版本 (5 个重大更新)
0.5.0 | 2024 年 5 月 23 日 |
---|---|
0.4.0 | 2024 年 4 月 18 日 |
0.3.0 | 2024 年 4 月 18 日 |
0.2.1 | 2024 年 4 月 15 日 |
0.0.0 | 2023 年 12 月 16 日 |
#160 在 数据库接口
每月 1,211 次下载
3MB
441 行
VennDB
一个使用位(标志)列查询的、仅追加的 Rust 内存数据库。此数据库适用于一个非常特定的用例,其中您拥有大部分静态数据,通常在启动时加载并需要使用非常简单的过滤器不断查询。此类数据集可能很大,应该既快速又紧凑。
对于 venndb 可以应用的一些有限用例,它的依赖项更少,比传统的选择(例如,原始实现或更重的依赖项,如 Sqlite)更快。
有关此主题的更多信息,请参阅基准测试。
该项目最初是在rama
的函数下开发的,您可以在其中看到它被用作示例,例如提供内存(上游)代理数据库。如果您也在您的项目中使用它,请告诉我们,这样我们就可以创建一个展示列表。
💬 加入我们Discord上的 #venndb
公共频道。提问,讨论想法,了解 venndb 如何对您有所帮助。
索引
venndb
手册
- 用法:如何使用
venndb
的快速介绍; - 基准测试:基准测试结果,为您提供一个关于
venndb
如何执行其针对用例(一次性写入,频繁读取,主要使用二进制过滤器)的粗略了解; - Q&A:常见问题解答(FAQ);
- 示例:完整示例(从 用法 扩展的版本),经过测试和文档化;
- 生成的代码摘要:当在您的命名字段结构上使用
#[derive)]
时,venndb
将为您生成的 API 的文档化概述;
技术信息
杂项
使用方法
将 venndb
添加为依赖项
cargo add venndb
并在您想要使用它的模块中导入 derive
宏
use venndb::VennDB
#[derive(Debug, VennDB)]
pub struct Employee {
#[venndb(key)]
id: u32,
name: String,
is_manager: Option<bool>,
is_admin: bool,
#[venndb(skip)]
foo: bool,
#[venndb(filter, any)]
department: Department,
#[venndb(filter)]
country: Option<String>,
}
fn main() {
let db = EmployeeDB::from_iter(/* .. */);
let mut query = db.query();
let employee = query
.is_admin(true)
.is_manager(false)
.department(Department::Engineering)
.execute()
.expect("to have found at least one")
.any();
println!("non-manager admin engineer: {:?}", employee);
}
请参阅下面的完整示例或“生成的代码摘要”章节,了解如何使用 VennDB
及其生成的代码。
基准测试
此处显示的基准测试是在具有以下规格的开发机器上进行的
Macbook Pro — 16 inch (2023)
Chip: Apple M2 Pro
Memory: 16 GB
OS: Sonoma 14.2
基准测试测试了三种不同的代理数据库实现
venndb
版本(与下面的示例非常相似)- 一个
naive
版本,它只是一个Vec<Proxy>
,对其迭代 - 一个
sqlite
版本(使用sqlite
仓库(版本:0.34.0
))
这些基准测试是由以下方式创建的
- 运行
just bench
; - 将输出复制到 ./scripts/plot_bench_charts 并运行它。
每个 3 种实现运行的代码片段
fn test_db(db: &impl ProxyDB) {
let i = next_round();
let pool = POOLS[i % POOLS.len()];
let country = COUNTRIES[i % COUNTRIES.len()];
let result = db.get(i as u64);
divan::black_box(result);
let result = db.any_tcp(pool, country);
divan::black_box(result);
let result = db.any_socks5_isp(pool, country);
divan::black_box(result);
}
基准测试性能结果
拥有 100
条记录的数据库的性能
代理数据库 | 最快(微秒) | 中位数(微秒) | 最慢(微秒) |
---|---|---|---|
naive_proxy_db_100 | 6.50 | 8.00 | 18.04 |
sql_lite_proxy_db_100 | 32.58 | 37.37 | 302.00 |
venn_proxy_db_100 | 0.89 | 0.92 | 2.74 |
拥有 12_500
条记录的数据库的性能
代理数据库 | 最快(微秒) | 中位数(微秒) | 最慢(微秒) |
---|---|---|---|
naive_proxy_db_12_500 | 404.00 | 407.70 | 478.70 |
sql_lite_proxy_db_12_500 | 1061.00 | 1073.00 | 1727.00 |
venn_proxy_db_12_500 | 16.04 | 16.97 | 25.54 |
拥有 100_000
条记录的数据库的性能
代理数据库 | 最快(微秒) | 中位数(微秒) | 最慢(微秒) |
---|---|---|---|
naive_proxy_db_100_000 | 3790.00 | 3837.00 | 5731.00 |
sql_lite_proxy_db_100_000 | 8219.00 | 8298.00 | 9424.00 |
venn_proxy_db_100_000 | 124.20 | 129.20 | 156.30 |
尽管我们不是数据库或硬件专家,但如果您认为这些基准测试不正确或有相关改进,请提出问题。我们同样欢迎以拉取请求的形式进行贡献。
有关更多信息,请参阅贡献指南。
基准测试分配结果
拥有 100
条记录的数据库的分配
代理数据库 | 最快(KB) | 中位数(KB) | 最慢(KB) |
---|---|---|---|
naive_proxy_db_100 | 0.33 | 0.33 | 0.33 |
sql_lite_proxy_db_100 | 4.04 | 4.04 | 4.04 |
venn_proxy_db_100 | 0.05 | 0.05 | 0.05 |
拥有 12_500
条记录的数据库的分配
代理数据库 | 最快(KB) | 中位数(KB) | 最慢(KB) |
---|---|---|---|
naive_proxy_db_12_500 | 40.73 | 40.73 | 40.73 |
sql_lite_proxy_db_12_500 | 5.03 | 5.02 | 5.03 |
venn_proxy_db_12_500 | 3.15 | 3.15 | 3.15 |
拥有 100_000
条记录的数据库的分配
代理数据库 | 最快(KB) | 中位数(KB) | 最慢(KB) |
---|---|---|---|
naive_proxy_db_100_000 | 323.30 | 323.30 | 323.70 |
sql_lite_proxy_db_100_000 | 5.02 | 5.02 | 5.01 |
venn_proxy_db_100_000 | 25.02 | 25.02 | 25.02 |
尽管我们不是数据库或硬件专家,但如果您认为这些基准测试不正确或有相关改进,请提出问题。我们同样欢迎以拉取请求的形式进行贡献。
有关更多信息,请参阅贡献指南。
问答
❓ 为什么使用这个而不是数据库 X?
venndb
不是一个数据库,但对于某些特定目的来说已经足够接近了。它在需要过滤大量二进制属性并获取随机匹配结果的长期只读使用场景中表现优异。
不要尝试用此来替代您通常需要的数据库。
❓ 我在哪里可以提出一个新功能 X 或其他改进建议?
另外,您也可以加入我们的Discord并在那里开始对话或讨论。
❓ 我可以使用任何类型的venndb属性吗?
是的,只要它实现了PartialEq + Eq + Hash + Clone
。尽管如此,我们仍然建议您在可能的情况下使用enum
值或其他高度受限的形式。
例如,直接使用String
是不明智的,因为这会导致bE
!= Be
!= BE
!= Belgium
!= Belgique
!= België
。尽管这些实际上都指的是同一个国家。在这种情况下,至少创建一个包装类型,如struct Country(String)
,以在创建值时强制执行清理/验证,并确保概念上相同的值的哈希值将相同。
❓ 我该如何使过滤器成为可选的?
过滤器(bool
属性)和过滤器映射(具有#[venndb(filter)]
属性的T != bool
属性)可以通过使用Option
来包裹类型,从而使其成为可选的,结果是Option<bool>
和Option<T>
。
对于具有此类可选列的Option::None
值的行,无法对该属性进行筛选,但除此之外没有其他后果。
❓ 为什么键必须唯一且非可选的?
在venndb
中,键是为了能够查找之前通过过滤器接收到的行。
因此,这样的键应该是
- 唯一的:因为这会导致返回多行或错误的行;
- 非可选的:因为这意味着在键未定义时无法查找该行;
❓ 我如何允许某些行与某个(过滤器)列的任何值匹配?
过滤器映射允许有一个值匹配所有其他值。您需要声明该过滤器为这样,并定义对于该类型,哪个是“统治一切”的值。
使用方法
use venndb::{Any, VennDB};
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum Department {
Any,
Hr,
Engineering,
}
impl Any for Department {
fn is_any(&self) -> bool {
self == Department::Any
}
}
#[derive(Debug, VennDB)]
pub struct Employee {
name: String,
#[venndb(filter, any)]
department: Department,
}
let db = EmployeeDB::from_iter([
Employee { name: "Jack".to_owned(), department: Department::Any },
Employee { name: "Derby".to_owned(), department: Department::Hr },
]);
let mut query = db.query();
// will match Jack and Derby, as Jack is marked as Any, meaning it can work for w/e value
let hr_employees: Vec<_> = query.department(Department::Hr).execute().unwrap().iter().collect();
assert_eq!(hr_employees.len(), 2);
❓ 我如何在将行附加之前提供自定义验证?
能否根据一行的一个或多个属性进行验证?基于多个属性之间的关系进行验证?能否提供自定义验证以防止不符合自定义验证规则的行被追加?
以上都是可以的。
示例
#[derive(Debug, VennDB)]
#[venndb(validator = my_validator_fn)]
pub struct Value {
pub foo: String,
pub bar: u32,
}
fn my_validator_fn(value: &Value) -> bool {
!value.foo.is_empty() && value.bar > 0
}
let mut db = ValueDB::default();
assert!(db.append(Value {
foo: "".to_owned(),
bar: 42,
}).is_err()); // fails because foo == empty
❓ 为什么
any
过滤值只匹配具有该属性any
值的行?
假设我有以下 struct
use venndb::{Any, VennDB};
#[derive(Debug, VennDB)]
pub struct Value {
#[venndb(filter, any)]
pub foo: MyString,
pub bar: u32,
}
#[derive(Debug)]
pub struct MyString(String);
impl Any for MyString {
fn is_any(&self) -> bool {
self.0 == "*"
}
}
let db = ValueDB::from_iter([
Value {
foo: MyString("foo".to_owned()),
bar: 8,
},
Value {
foo: MyString("*".to_owned()),
bar: 16,
}
].into_Iter()).unwrap();
let mut query = db.query();
query.foo(MyString("*".to_owned()));
let value = query.execute().unwrap().any();
// this will never match the row with bar == 8,
// tiven foo != an any value
assert_eq!(value.bar, 16);
这是为什么?因为它是对的。
允许它也匹配值 foo
会对 foo
相对于 任何 值的选择机会产生不公平的影响。这看起来可能不是一个很大的区别,但它是。因为如果我们为 Value
生成一个随机的字符串,并且有一个 _any 值,如果我们允许所有行都匹配,那么这种逻辑现在就是被操纵的,foo
的值比其他字符串更有可能。
因此,对于 任何 值的过滤,唯一正确的答案就是返回具有 任何 值的行。
❓ 我如何对“过滤器映射”属性的多个变体进行查询?
只需多次调用 query 方法。这将允许您匹配具有这些值之一的行。
示例
use venndb::{Any, VennDB};
#[derive(Debug, VennDB)]
pub struct Value {
#[venndb(filter)]
pub foo: String,
pub bar: u32,
}
let db = ValueDB::from_iter([
Value {
foo: "a".to_owned(),
bar: 8,
},
Value {
foo: "b".to_owned(),
bar: 12,
},
Value {
foo: "c".to_owned(),
bar: 16,
},
].into_Iter()).unwrap();
let mut query = db.query();
query.foo(MyString("a".to_owned()));
query.foo(MyString("c".to_owned()));
let values: Vec<_> = query.execute().unwrap().iter().collect();
assert_eq!(values.len(), 2);
assert_eq!(values[0].bar, 8);
assert_eq!(values[0].bar, 16);
示例
以下是一个示例,展示了 VennDB
的所有功能。
如果您希望查看生成的总结,或者不理解以下示例中的某些内容,您还可以阅读下面的 “生成代码总结”章节。
use itertools::Itertools;
use venndb::VennDB;
#[derive(Debug, VennDB)]
// These attributes are optional,
// e.g. by default the database would be called `EmployeeDB` (name + 'DB').
#[venndb(name = "EmployeeInMemDB", validator = employee_validator)]
pub struct Employee {
// you can use the `key` arg to be able to get an `Employee` instance
// directly by this key. It will effectively establishing a mapping from key to a reference
// of that Employee in the database. As such keys have to have unique values,
// or else you get an error while appending / creating the DB.
//
// NOTE: keys do not only have to be unique, they also have to implement `Clone`!!
//
// A property cannot be a filter and a key at the same time,
// trying to do so will result in a compile-team failure.
#[venndb(key)]
id: u32,
name: String,
is_manager: bool,
is_admin: bool,
// filter (booleans) can be made optional,
// meaning that the row will not be able to be filtered (found)
// on this column when the row has a `None` value for it
is_active: Option<bool>,
// booleans are automatically turned into (query) filters,
// use the `skip` arg to stop this. As such it is only really needed for
// bool properties :)
#[venndb(skip)]
foo: bool,
// non-bool values can also be turned into filters, turning them into 2D filters.
// For each uniquely inserted Department variant that is inserted,
// a new filter is kept track of. This allows you to apply a (query) filter
// based on department, a pretty useful thing to be able to do.
//
// NOTE: this does mean that such filter-map types have to also be:
// `PartialEq + Eq + Hash + Clone`!!
//
// A property cannot be a filter and a key at the same time,
// trying to do so will result in a compile-team failure.
#[venndb(filter)]
department: Department,
// similar to regular bool filters,
// filter maps can also be optional.
// When a filter map is optional and the row's property for that filter is None,
// it will not be registered and thus not be able to filtered (found) on that property
#[venndb(filter)]
country: Option<String>,
}
fn employee_validator(employee: &Employee) -> bool {
employee.id > 0
}
fn main() {
let db = EmployeeInMemDB::from_iter([
RawCsvRow("1,John Doe,true,false,true,false,Engineering,USA"),
RawCsvRow("2,Jane Doe,false,true,true,true,Sales,"),
RawCsvRow("3,John Smith,false,false,,false,Marketing,"),
RawCsvRow("4,Jane Smith,true,true,false,true,HR,"),
RawCsvRow("5,John Johnson,true,true,true,true,Engineering,"),
RawCsvRow("6,Jane Johnson,false,false,,false,Sales,BE"),
RawCsvRow("7,John Brown,true,false,true,false,Marketing,BE"),
RawCsvRow("8,Jane Brown,false,true,true,true,HR,BR"),
])
.expect("MemDB created without errors (e.g. no duplicate keys)");
println!(">>> Printing all employees...");
let all_employees: Vec<_> = db.iter().collect();
assert_eq!(all_employees.len(), 8);
println!("All employees: {:#?}", all_employees);
println!(">>> You can lookup an employee by any registered key...");
let employee = db
.get_by_id(&2)
.expect("to have found an employee with ID 2");
assert_eq!(employee.name, "Jane Doe");
println!(">>> Querying for all managers...");
let mut query = db.query();
query.is_manager(true);
let managers: Vec<_> = query
.execute()
.expect("to have found at least one")
.iter()
.collect();
assert_eq!(managers.len(), 4);
assert_eq!(
managers.iter().map(|e| e.id).sorted().collect::<Vec<_>>(),
[1, 4, 5, 7]
);
println!(">>> Querying for all managers with a last name of 'Johnson'...");
let managers_result = query
.execute()
.expect("to have found at least one")
.filter(|e| e.name.ends_with("Johnson"))
.expect("to have found a manager with a last name of Johnson");
let managers = managers_result.iter().collect::<Vec<_>>();
assert_eq!(managers.len(), 1);
assert_eq!(managers.iter().map(|e| e.id).collect::<Vec<_>>(), [5]);
println!(">>> You can also just get the first result if that is all you care about...");
let manager = managers_result.first();
assert_eq!(manager.id, 5);
println!(">>> Querying for a random active manager in the Engineering department...");
let manager = query
.reset()
.is_active(true)
.is_manager(true)
.department(Department::Engineering)
.execute()
.expect("to have found at least one")
.any();
assert!(manager.id == 1 || manager.id == 5);
println!(">>> Optional bool filters have three possible values, where None != false. An important distinction to make...");
let mut query = db.query();
query.is_active(false);
let inactive_employees: Vec<_> = query
.execute()
.expect("to have found at least one")
.iter()
.collect();
assert_eq!(inactive_employees.len(), 1);
assert_eq!(inactive_employees[0].id, 4);
println!(">>> If you want you can also get the Employees back as a Vec, dropping the DB data all together...");
let employees = db.into_rows();
assert_eq!(employees.len(), 8);
assert!(employees[1].foo);
println!("All employees: {:?}", employees);
println!(">>> You can also get the DB back from the Vec, if you want start to query again...");
// of course better to just keep it as a DB to begin with, but let's pretend this is ok in this example
let mut db = EmployeeInMemDB::from_rows(employees).expect("DB created without errors");
assert_eq!(db.iter().count(), 8);
println!(">>> Querying for all active employees in the Sales department...");
let mut query = db.query();
query.is_active(true);
query.department(Department::Sales);
let sales_employees: Vec<_> = query
.execute()
.expect("to have found at least one")
.iter()
.collect();
assert_eq!(sales_employees.len(), 1);
assert_eq!(sales_employees[0].name, "Jane Doe");
println!(">>> Filter maps that are optional work as well, e.g. you can query for all employees from USA...");
query.reset().country("USA".to_owned());
let usa_employees: Vec<_> = query
.execute()
.expect("to have found at least one")
.iter()
.collect();
assert_eq!(usa_employees.len(), 1);
assert_eq!(usa_employees[0].id, 1);
println!(">>> At any time you can also append new employees to the DB...");
assert_eq!(EmployeeInMemDBErrorKind::DuplicateKey, db
.append(RawCsvRow("8,John Doe,true,false,true,false,Engineering,"))
.unwrap_err().kind());
println!(">>> This will fail however if a property is not correct (e.g. ID (key) is not unique in this case), let's try this again...");
assert!(db
.append(RawCsvRow("9,John Doe,false,true,true,false,Engineering,"))
.is_ok());
assert_eq!(db.len(), 9);
println!(">>> Rows are also validated prior to appending in case a validator is defined...");
println!(" The next insertion will fail due to the id being zero, a condition defined in the custom validator...");
assert_eq!(EmployeeInMemDBErrorKind::InvalidRow, db
.append(RawCsvRow("0,John Doe,true,false,true,false,Engineering,"))
.unwrap_err().kind());
println!(">>> This new employee can now also be queried for...");
let mut query = db.query();
query.department(Department::Engineering).is_manager(false);
let new_employee: Vec<_> = query
.execute()
.expect("to have found at least one")
.iter()
.collect();
assert_eq!(new_employee.len(), 1);
assert_eq!(new_employee[0].id, 9);
println!(">>> You can also extend it using an IntoIterator...");
db.extend([
RawCsvRow("10,Glenn Doe,false,true,true,true,Engineering,"),
RawCsvRow("11,Peter Miss,true,true,true,true,HR,USA"),
])
.unwrap();
let mut query = db.query();
query
.department(Department::HR)
.is_manager(true)
.is_active(true)
.is_admin(true);
let employees: Vec<_> = query
.execute()
.expect("to have found at least one")
.iter()
.collect();
assert_eq!(employees.len(), 1);
assert_eq!(employees[0].id, 11);
println!(">>> There are now 2 employees from USA...");
query.reset().country("USA".to_owned());
let employees: Vec<_> = query
.execute()
.expect("to have found at least one")
.iter()
.collect();
assert_eq!(employees.len(), 2);
assert_eq!(
employees.iter().map(|e| e.id).sorted().collect::<Vec<_>>(),
[1, 11]
);
println!(">>> All previously data is still there as well of course...");
query
.reset()
.is_active(true)
.is_manager(true)
.department(Department::Engineering);
let managers: Vec<_> = query
.execute()
.expect("to have found at least one")
.iter()
.collect();
assert_eq!(managers.len(), 2);
assert_eq!(
managers.iter().map(|e| e.id).sorted().collect::<Vec<_>>(),
[1, 5]
);
}
#[derive(Debug)]
struct RawCsvRow<S>(S);
impl<S> From<RawCsvRow<S>> for Employee
where
S: AsRef<str>,
{
fn from(RawCsvRow(s): RawCsvRow<S>) -> Employee {
let mut parts = s.as_ref().split(',');
let id = parts.next().unwrap().parse().unwrap();
let name = parts.next().unwrap().to_string();
let is_manager = parts.next().unwrap().parse().unwrap();
let is_admin = parts.next().unwrap().parse().unwrap();
let is_active = match parts.next().unwrap() {
"" => None,
s => Some(s.parse().unwrap()),
};
let foo = parts.next().unwrap().parse().unwrap();
let department = parts.next().unwrap().parse().unwrap();
let country = match parts.next().unwrap() {
"" => None,
s => Some(s.to_string()),
};
Employee {
id,
name,
is_manager,
is_admin,
is_active,
foo,
department,
country,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum Department {
Engineering,
Sales,
Marketing,
HR,
}
impl std::str::FromStr for Department {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Engineering" => Ok(Department::Engineering),
"Sales" => Ok(Department::Sales),
"Marketing" => Ok(Department::Marketing),
"HR" => Ok(Department::HR),
_ => Err(()),
}
}
}
生成代码总结
在本章中,我们将列出 VennDB
生成以下示例代码的 API。
#[derive(Debug, VennDB)]
#[venndb(name = "EmployeeInMemDB", validator = employee_validator)]
pub struct Employee {
#[venndb(key)]
id: u32,
name: String,
is_manager: bool,
is_admin: bool,
is_active: Option<bool>,
#[venndb(skip)]
foo: bool,
#[venndb(filter)]
department: Department,
country: Option<String>,
}
将生成以下公共 API 数据结构
struct EmployeeInMemDB
:数据库,可用于查询(通过过滤器)或查找数据(通过键);enum EmployeeInMemDBError
:在修改数据库和要插入行的属性时返回的错误类型;enum EmployeeInMemDBErrorKind
:如EmployeeInMemDBError
中所述可能发生的错误类型;struct EmployeeInMemDBQuery
:用于构建可以execute
以使用过滤器从数据库中查询数据的查询构建器;struct EmployeeInMemDBQueryResult
:使用EmployeeInMemDBQuery
查询并至少找到一行匹配定义的过滤器时的结果;struct EmployeeInMemDBQueryResultIter
:当调用EmployeeInMemDBQueryResult::iter
时使用的迭代器类型。它没有其他方法/api,除了它是一个Iterator
并可以像这样使用。
这些数据结构的视觉指定符将与应用于 struct
的 VennDB
宏相同。例如,在这个例子中,Employee
有一个指定符 pub
,因此上面的数据结构和它们的公共方法也将是 pub
。
还生成了其他一些辅助数据结构——所有都以数据库名称作为前缀,例如本例中的 EmployeeInMemDB
——但我们在这里不提及它们,因为不应依赖于它们,并且由于前缀,它们应该不会引起冲突。如果您不想将这些结构公开给外部,您可以将您的 struct
包裹在其自己的 mod
(模块)中。
生成的代码摘要:方法API
数据库:(例如 EmployeeInMemDB
)
函数签名 | 描述 |
---|---|
EmployeeInMemDB::new() ->EmployeeInMemDB |
创建一个容量为零的新数据库 |
EmployeeInMemDB::默认() ->EmployeeInMemDB |
与 EmployeeInMemDB::new() -> EmployeeInMemDB 相同 |
EmployeeInMemDB::capacity(capacity: usize) ->EmployeeInMemDB |
创建一个具有给定容量但尚未插入任何行的数据库 |
EmployeeInMemDB::from_rows(rows: ::std::vec::Vec<Employee>) -> EmployeeInMemDB 或 EmployeeInMemDB::from_rows(rows: ::std::vec::Vec<Employee>) -> Result<EmployeeInMemDB, EmployeeInMemDBError<::std::vec::Vec<Employee>>> |
构造函数,直接从堆分配的数据实例列表创建数据库。如果至少定义了一个 #[venndb)] 属性,则使用第二个版本,否则使用第一个(没有 Result )。 |
EmployeeInMemDB::from_iter(iter: impl ::std::iter::IntoIterator<Item = impl ::std::convert::Into<Employee>>) -> EmployeeInMemDB 或 EmployeeInMemDB::from_rows(iter: impl ::std::iter::IntoIterator<Item = impl ::std::convert::Into<Employee>>) -> Result<EmployeeInMemDB, EmployeeInMemDBError<::std::vec::Vec<Employee>>> |
与 from_rows 相同,但使用迭代器。项目不一定是 Employee ,但可以是任何可以转换为它的东西。例如,在上述示例中,我们定义了一个结构体 RawCsvRow ,它在插入数据库之前即时转换为 Employee 。这是因为返回带有结果的版本会返回一个 Vec 而不是迭代器。 |
EmployeeInMemDB::append(&mut self, data: impl ::std::convert::Into<Employee>) 或 EmployeeInMemDB::append(&mut self, data: impl ::std::convert::Into<Employee>) -> Result<(), EmployeeInMemDBError<Employee>> |
将单行添加到数据库中。根据是否定义了 #[venndb(key)] 属性,它将生成带有结果的版本或不带结果的版本。与 from_rows 和 from_iter 相同。 |
EmployeeInMemDB::extend<I, Item>(&mut self, iter: I) where I: ::std::iter::IntoIterator<Item = Item>, Item: ::std::convert::Into<Employee> 或 EmployeeInMemDB::extend<I, Item>(&mut self, iter: I) -> Result<(), EmployeeInMemDBError<(Employee, I::IntoIter)>> where I: ::std::iter::IntoIterator<Item = Item>, Item: ::std::convert::Into<Employee> |
使用给定的迭代器扩展数据库,如果插入可能出错(例如,因为使用了键(重复)或定义了验证器时行无效),则再次返回一个结果。否则,此函数将不返回任何内容。 |
EmployeeInMemDB::通过_id获取<Q>(&self,数据: 实现 ::std::转换::Into<Employee>) -> Option<&Employee> whereEmployee::std::borrow::Borrow<Q>,Q: ::std::hash::Hash +::std::cmp::Eq + ?::std::marker::Sized |
通过 id 键属性查找行。此方法将为每个带有 #[venndb(key) 标记的属性生成。例如,如果你有一个名为 foo: MyType 的属性,将生成一个 get_by_foo(&self, ...) 方法。 |
EmployeeInMemDB::查询(&self) ->EmployeeInMemDBQuery |
创建一个 EmployeeInMemDBQuery 构建器来组合一个过滤器组合以查询数据库。默认构建器将匹配所有行。有关 EmployeeInMemDBQuery 的方法 API,请参阅更多信息 |
查询(例如 EmployeeInMemDBQuery
)
函数签名 | 描述 |
---|---|
EmployeeInMemDBQuery::reset(&mut self) -> &mut Self |
重置查询,将其恢复到创建时的干净状态 |
EmployeeInMemDBQuery::execute(&self) -> Option<EmployeeInMemDBQueryResult<'a>> |
使用集合过滤器返回查询结果。如果没有任何行匹配定义的过滤器,则将为 None 。换句话说,当返回 Some(_) 时,结果将至少包含一行。 |
EmployeeInMemDBQuery::is_manager(&mut self,value: bool) -> &mut Self |
一个用于布尔过滤器(bool )的过滤器设置器。对于每个(非 skip ped)布尔过滤器,将有一个这样的方法。例如,如果您有一个 foo 过滤器,则将有一个 EmployeeInMemDBQuery:foo 方法。对于可选(Option<bool> )布尔过滤器,此方法也将按相同方式生成。 |
EmployeeInMemDBQuery::department(&mut self,value: 实现 ::std::转换::Into<Department>) -> &mut Self |
一个用于非布尔过滤器的过滤器(映射)设置器。对于每个非布尔过滤器,将有一个这样的方法。您也可以 skip 这些,但这当然有点没有意义。类型将与实际字段类型相同。并且名称将再次与原始字段名称相同。类型为 Option<T> 的过滤器映射具有完全相同的签名。在查询期间,如果您希望允许多个变体,则可以多次调用此方法。 |
查询结果(例如 EmployeeInMemDBQueryResult
)
函数签名 | 描述 |
---|---|
EmployeeInMemDBQueryResult::first(&self) -> &Employee |
返回找到的第一个匹配员工引用。实现细节是这将是最先插入的匹配行,但出于兼容性原因,如果您不需要,最好不要依赖于此。 |
EmployeeInMemDBQueryResult::any(&self) -> &Employee |
返回一个随机选择的匹配员工引用。可以信赖随机性是公平的。 |
EmployeeInMemDBQueryResult::iter(&self) -> EmployeeInMemDBQueryResultIter` |
返回查询结果的迭代器,允许您遍历所有找到的结果,并且如果需要,还可以将它们收集到自己的数据结构中。 |
EmployeeInMemDBQueryResult::filter<F>(&self,谓词:F) -> Option<#EmployeeInMemDBQueryResult> whereF: Fn(&#名称) -> bool |
返回具有相同引用数据的 Some(_) EmployeeInMemDBQueryResult ,但仅包含(并拥有)与给定 Fn 谓词匹配的索引。 |
⛨ | 安全性
此crate使用 #![forbid(unsafe_code)]
来确保一切都在100%安全的Rust中实现。
🦀 | 兼容性
venndb主要在MacOS M系列机器上开发,并在各种Linux系统上运行。Windows支持未正式保证,但已成功使用Github Actions进行测试。
平台 | 已测试 | 测试平台 |
---|---|---|
MacOS | ✅ | M2(开发笔记本电脑)和macos-12 Intel(GitHub Action) |
Windows | ✅ | Windows 2022(GitHub Action) |
Linux | ✅ | Ubuntu 22.04(《GitHub Action》) |
如果您在您的设置/平台上遇到兼容性问题,请提交工单。我们的目标不是支持世界上所有可能的平台,但我们确实希望尽可能地支持。
最低支持的Rust版本
venndb的MSRV是1.75
。
我们使用GitHub Actions进行测试,以确保该版本的venndb
仍然在rust的稳定和测试版本上工作。
🧭 | 路线图
请参阅https://github.com/plabayo/venndb/milestones以了解路线图。下一个版本是否有不在路线图上的功能,您真的想要?请创建功能请求,并在可能的情况下成为赞助商。
💼 | 许可证
此项目同时受MIT许可和Apache 2.0许可证的双重许可。
👋 | 贡献
🎈 感谢您帮助改进项目!我们很高兴有您!我们有一个贡献指南,帮助您参与venndb
项目。
贡献通常来自那些已经知道他们想要什么的人,无论是他们遇到的bug的修复,还是他们缺少的功能。请始终在不存在工单的情况下提交工单。
然而,您可能还不知道具体要贡献什么,但仍然想帮忙。为此,我们感谢您。您可以查看开放的问题,特别是
good first issue
:适合新接触venndb
代码库的问题;easy
:被认为简单的问题;mentor available
:我们提供指导的问题;low prio
:优先级低的问题,没有立即完成的需求,如果您有时间,非常适合帮忙;
一般来说,任何未分配的问题都可以由其他人接手。如果您打算接手,请与工单沟通,以避免多人试图解决同一个问题。
如果您想为该项目做出贡献,但不知道如何用Rust编程,您可以使用"Rust 101学习指南"作为学习伴侣,开始学习Rust,目标是尽快为venndb
做出贡献。Glen也可以作为导师或教师,提供付费的一对一课程和其他类似咨询服务。您可以在https://www.glendc.com/找到他的联系信息。
贡献
除非您明确表示,否则您提交给venndb
的任何有意贡献都将同时以MIT和Apache 2.0许可,无任何额外条款或条件。
致谢
特别感谢所有参与开发、维护和支持 Rust 编程语言 的人。同时向 《编写强大的 Rust 宏》 一书作者 Sam Van Overmeire 表示敬意,这本书给了我们开发这个crate的勇气。
部分代码也复制/分叉自 google/argh,对此表示感谢,我们是这个crate的忠实粉丝。如果你想要创建一个 CLI 应用程序,就去使用它吧。
💖 | 赞助商
venndb 是一款 完全免费、开源的软件,它的开发和维护需要大量的努力和时间。
通过成为 赞助商 来支持本项目。一次性付款可以通过 GitHub 以及 “Buy me a Coffee” 进行。
赞助商帮助我们继续维护和改进 venndb
,以及其他自由和开源(FOSS)技术。它还帮助我们创建教育内容,例如 https://github.com/plabayo/learn-rust-101,以及其他开源框架,例如 https://github.com/plabayo/rama。
赞助商将获得一些特权,并且根据您的定期贡献,这也使您能够依赖我们进行支持和咨询。
最后,您还可以通过在 https://plabayo.threadless.com/ 购买 Plabayo <3 VennDB
周边商品来支持我们。