Skip to content

Commit

Permalink
Merge pull request #9 from plabayo/release/0.4.0
Browse files Browse the repository at this point in the history
v0.4.0 branch
  • Loading branch information
GlenDC authored Apr 18, 2024
2 parents c5d4f5f + 17b496b commit 1305a46
Show file tree
Hide file tree
Showing 13 changed files with 691 additions and 71 deletions.
37 changes: 36 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,44 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

# 0.4.0 (2024-04-19)

Breaking Changes:

* [[`#7`](https://github.com/plabayo/venndb/issues/7)]: correct the behaviour of any filter map query values:
- When using an any value as a query filter map value it will now only match rows
which have an any value registered for the row;
- Prior to this release it was matching on all rows, as if the filter wasn't defined.
This seemed correct when deciding on it, but on hindsight is is incorrect behaviour.

New Features:

* [[`#8`](https://github.com/plabayo/venndb/issues/8)]: support custom validations of rows prior to appending them

Example:

```rust
#[derive(Debug, VennDB)]
#[venndb(name = "MyDB", 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 = MyDB::default();
assert!(db.append(Value {
foo: "".to_owned(),
bar: 42,
}).is_err()); // fails because foo == empty
```

# 0.3.0 (2024-04-18)

Breaking changes:
Breaking Changes:

* [[`#6`](https://github.com/plabayo/venndb/issues/6)] query filter maps now accept arguments as `impl Into<T>` instead of `T`,
this can be a breaking change for users that were inserting them as `value.into()`,
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ repository = "https://github.com/plabayo/venndb"
keywords = ["database", "db", "memory", "bits"]
categories = ["database"]
authors = ["Glen De Cauwsemaecker <glen@plabayo.tech>"]
version = "0.3.0"
version = "0.4.0"
rust-version = "1.75.0"

[package.metadata.docs.rs]
Expand All @@ -23,7 +23,7 @@ rustdoc-args = ["--cfg", "docsrs"]
bitvec = "1.0.1"
hashbrown = "0.14.3"
rand = "0.8.5"
venndb-macros = { version = "0.3.0", path = "venndb-macros" }
venndb-macros = { version = "0.4.0", path = "venndb-macros" }

[dev-dependencies]
divan = "0.1.14"
Expand Down
103 changes: 98 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,89 @@ let hr_employees: Vec<_> = query.department(Department::Hr).execute().unwrap().i
assert_eq!(hr_employees.len(), 2);
```

> ❓ How can I provide custom validation of rows prior to them getting appended?
Is is possible to validate a row based on one or multiple of its properties? Validate in function of relationship
between multiple properties? Is it possible to provide custom validation to prevent rows
from getting appended that do not adhere to custom validation rules?

Yes to all of the above.

Example:

```rust,ignore
#[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
```

> ❓ Why do `any` filter values only match rows that have an `any` value for that property?
Let's say I have the following `struct`:

```rust,ignore
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);
```

Why is this true? Because it is correct.

Allowing it also to match the value `foo` would unfairly
give more chances for `foo` to be selected over the _any_ value.
This might not seem like a big difference, but it is. Because what if
we generate a random string for `Value`s with an _any value? If we
would allow all rows to be matched then that logic is now rigged,
with a value of `foo` being more likely then other strings.

As such the only correct answer when filtering for _any_ value,
is to return rows that have _any_ value.

## Example

Here follows an example demonstrating all the features of `VennDB`.
Expand All @@ -318,7 +401,7 @@ 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")]
#[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
Expand Down Expand Up @@ -363,6 +446,10 @@ pub struct Employee {
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"),
Expand Down Expand Up @@ -471,15 +558,21 @@ fn main() {
assert_eq!(usa_employees[0].id, 1);

println!(">>> At any time you can also append new employees to the DB...");
assert!(db
assert_eq!(EmployeeInMemDBErrorKind::DuplicateKey, db
.append(RawCsvRow("8,John Doe,true,false,true,false,Engineering,"))
.is_err());
.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);
Expand Down Expand Up @@ -607,7 +700,7 @@ In this chapter we'll list the API as generated by `VennDB` for the following ex

```rust,ignore
#[derive(Debug, VennDB)]
#[venndb(name = "EmployeeInMemDB")]
#[venndb(name = "EmployeeInMemDB", validator = employee_validator)]
pub struct Employee {
#[venndb(key)]
id: u32,
Expand Down Expand Up @@ -651,7 +744,7 @@ Database: (e.g. `EmployeeInMemDB`):
| `EmployeeInMemDB::from_rows(rows: ::std::vec::Vec<Employee>) -> EmployeeInMemDB` or `EmployeeInMemDB::from_rows(rows: ::std::vec::Vec<Employee>) -> Result<EmployeeInMemDB, EmployeeInMemDBError<::std::vec::Vec<Employee>>>` | constructor to create the database directly from a heap-allocated list of data instances. The second version is the one used if at least one `#[venndb(key)]` property is defined, otherwise it is the first one (without the `Result`). |
| `EmployeeInMemDB::from_iter(iter: impl ::std::iter::IntoIterator<Item = impl ::std::convert::Into<Employee>>) -> EmployeeInMemDB` or `EmployeeInMemDB::from_rows(iter: impl ::std::iter::IntoIterator<Item = impl ::std::convert::Into<Employee>>) -> Result<EmployeeInMemDB, EmployeeInMemDBError<::std::vec::Vec<Employee>>>` | Same as `from_rows` but using an iterator instead. The items do not have to be an `Employee` but can be anything that can be turned into one. E.g. in our example above we defined a struct `RawCsvRow` that was turned on the fly into an `Employee`. This happens all at once prior to inserting the database, which is why the version with a result does return a `Vec` and not an iterator. |
| `EmployeeInMemDB::append(&mut self, data: impl ::std::convert::Into<Employee>)` or `EmployeeInMemDB::append(&mut self, data: impl ::std::convert::Into<Employee>) -> Result<(), EmployeeInMemDBError<Employee>>` | append a single row to the database. Depending on whether or not a `#[venndb(key)]` property is defined it will generate the `Result` version or not. Same as `from_rows` and `from_iter` |
| `EmployeeInMemDB::extend<I, Item>(&mut self, iter: I) where I: ::std::iter::IntoIterator<Item = Item>, Item: ::std::convert::Into<Employee>` or `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>` | extend the database with the given iterator, once again returning a result in case such insertion can go wrong (e.g. because keys are used (duplication)). Otherwise this function will return nothing. |
| `EmployeeInMemDB::extend<I, Item>(&mut self, iter: I) where I: ::std::iter::IntoIterator<Item = Item>, Item: ::std::convert::Into<Employee>` or `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>` | extend the database with the given iterator, once again returning a result in case such insertion can go wrong (e.g. because keys are used (duplication) or a row is invalid in case a validator is defined). Otherwise this function will return nothing. |
| `EmployeeInMemDB::get_by_id<Q>(&self, data: impl ::std::convert::Into<Employee>) -> Option<&Employee> where Employee ::std::borrow::Borrow<Q>, Q: ::std::hash::Hash + ::std::cmp::Eq + ?::std::marker::Sized` | look up a row by the `id` key property. This method will be generated for each property marked with `#[venndb(key)`. e.g. if you have key property named `foo: MyType` property there will be also a `get_by_foo(&self, ...)` method generated. |
| `EmployeeInMemDB::query(&self) -> EmployeeInMemDBQuery` | create a `EmployeeInMemDBQuery` builder to compose a filter composition to query the database. The default builder will match all rows. See the method API for `EmployeeInMemDBQuery` for more information |

Expand Down
2 changes: 1 addition & 1 deletion venndb-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ repository = "https://github.com/plabayo/venndb"
keywords = ["database", "db", "memory", "bits"]
categories = ["database", "db"]
authors = ["Glen De Cauwsemaecker <glen@plabayo.tech>"]
version = "0.3.0"
version = "0.4.0"
rust-version = "1.75.0"

[package.metadata.docs.rs]
Expand Down
76 changes: 76 additions & 0 deletions venndb-macros/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ impl Errors {
),
];

pub fn expect_path<'a>(&self, e: &'a syn::Expr) -> Option<&'a syn::Path> {
if let syn::Expr::Path(path) = e {
Some(&path.path)
} else {
self.unexpected_value("path", e);
None
}
}

fn unexpected_lit(&self, expected: &str, found: &syn::Expr) {
fn lit_kind(lit: &syn::Lit) -> &'static str {
use syn::Lit::{Bool, Byte, ByteStr, Char, Float, Int, Str, Verbatim};
Expand Down Expand Up @@ -126,6 +135,73 @@ impl Errors {
)
}

fn unexpected_value(&self, expected: &str, found: &syn::Expr) {
fn expr_kind(expr: &syn::Expr) -> &'static str {
use syn::Expr::{
Array, Assign, Async, Await, Binary, Block, Break, Call, Cast, Closure, Const,
Continue, Field, ForLoop, Group, If, Index, Infer, Let, Lit, Loop, Macro, Match,
MethodCall, Paren, Path, Range, Reference, Repeat, Return, Struct, Try, TryBlock,
Tuple, Unary, Unsafe, Verbatim, While, Yield,
};
match expr {
Array(_) => "array",
Assign(_) => "assignment",
Async(_) => "async block",
Await(_) => "await",
Binary(_) => "binary operation",
Block(_) => "block",
Break(_) => "break",
Call(_) => "function call",
Cast(_) => "cast",
Closure(_) => "closure",
Const(_) => "const",
Continue(_) => "continue",
Field(_) => "field access",
ForLoop(_) => "for loop",
Group(_) => "group",
If(_) => "if",
Index(_) => "index",
Infer(_) => "inferred type",
Let(_) => "let",
Lit(_) => "literal",
Loop(_) => "loop",
Macro(_) => "macro",
Match(_) => "match",
MethodCall(_) => "method call",
Paren(_) => "parentheses",
Path(_) => "path",
Range(_) => "range",
Reference(_) => "reference",
Repeat(_) => "repeat",
Return(_) => "return",
Struct(_) => "struct",
Try(_) => "try",
TryBlock(_) => "try block",
Tuple(_) => "tuple",
Unary(_) => "unary operation",
Unsafe(_) => "unsafe block",
Verbatim(_) => "verbatim",
While(_) => "while",
Yield(_) => "yield",
_ => "unknown expression kind",
}
}

self.err(
found,
&[
"Expected ",
expected,
" attribute, found ",
found.to_token_stream().to_string().as_str(),
" attribute (",
expr_kind(found),
")",
]
.concat(),
)
}

/// Issue an error relating to a particular `Spanned` structure.
pub fn err(&self, spanned: &impl syn::spanned::Spanned, msg: &str) {
self.err_span(spanned.span(), msg);
Expand Down
Loading

0 comments on commit 1305a46

Please sign in to comment.