vincent-herlemont / native_db

Drop-in embedded database in Rust
MIT License
433 stars 17 forks source link

Multiple secondary keys from a single record #143

Closed leontoeides closed 1 day ago

leontoeides commented 5 months ago

According to the documentation regarding secondary keys a custom function can be defined in the macro that can be used to finagle a secondary key from the user's struct.

The following tells native_db to use the user-supplied function custom_name to get the secondary key for the struct:

#[derive(Serialize, Deserialize)]
#[native_model(id=1, version=1)]
#[native_db(secondary_key(custom_name, optional))]
struct Data {
    #[primary_key]
    id: u64,
    #[secondary_key]
    name: String,
    flag: bool,
}

I wonder, is #[secondary_key] necessary if #[native_db(secondary_key(custom_name, optional))] is defined?

The user-defined custom_name function returns an Option<String> as the secondary key:

impl Data {
    fn custom_name(&self) -> Option<String> {
        if self.flag {
            Some(self.name.clone().to_uppercase())
        } else {
            None
        }
    }
}

Since, according to the documentation, users can define "define it or not, define one or more" I assume it should be possible to return multiple secondary keys for a single struct?

If so, I'd like to propose an additional secondary_keys definition that can return a Vec<T> rather than an Option<T>. If the secondary key isn't optional, an empty Vec would fail.

I get the impression that native_db's internal structure should support this, it's just a matter of implementing the feature? Any thoughts?

vincent-herlemont commented 5 months ago

I wonder, is #[secondary_key] necessary if #[native_db(secondary_key(custom_name, optional))] is defined?

Actually, no, it's not linked. You can have as many secondary_key associated with fields as you have custom secondary_key defined in the macro.

Here are some examples:

Each time it is a new key whether it is custom or not, you can have a mix of the two without any problem. And you can create as many as you want.

Does this answer your questions?

leontoeides commented 4 months ago

Thank you, yes that did answer my question.

I still think it would be very useful to be able to return an arbitrary number of secondary keys for a record using a function or by implementing a trait for the struct?

Just a hypothetical example here - let's say each struct in the database represented a country. A person could use an enum as a key:

enum CountryKey {
    City(String)
    State(String)
}

struct CountryValue {
    cities: Vec<String>,
    states: Vec<String>,
}

let united_states = CountryValue {
    cities: vec!["Houston", "San Antonio", "Galveston"], // etc.
    states: vec!["Texas"], // etc.
}

The custom #[native_db(secondary_keys(function_name))] function could return a collection of cities and states for the CountryValue. For example:

[City("Houston"), City("San Antonio"), City("Galveston"), State("Texas")]

And then when searching the database for City("Houston") or State("Texas") the database would return united_states struct.

vincent-herlemont commented 4 months ago

It's an interesting idea, I see several things in your example:

  1. The fact of having multiple values available for search for a single secondary key. And the possibility to search by value.
  2. The fact that a custom type like City is used as a key.