vincent-herlemont / native_db

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

Feature request: Query macro #210

Closed C4Phoenix closed 1 month ago

C4Phoenix commented 2 months ago

I would like to make the database interaction a bit more ergonomic and am working on completing a declarative macro. It looks like other people are also making wrappers around the API at the moment so I was hoping this could be included in the repo here. I can also publish it in a different crate (/repo) if you'd like too keep this a bit more centered.

What I have currently looks something like this:

macro_rules! use_db {
    (  // Get an item from the database
        $db:ident ==> <$itemType:ty>::($keyValue:expr $(=> $secondaryFieldKey:expr)?)
                // $(.$linkItem:ident $(-> <$linkItemTypeTo:ty>::[_ $(= $secondaryLinkKeyTo:expr)?])?
                //                    $(<- <$linkItemTypeFrom:ty>::[$secondaryLinkKeyFrom:expr])?)*
                $(::{ $($returnItem:ident),+})?
    ) => {..};
    (  // get items from the database
        $db:ident ==> <$itemType:ty>::[$($keyValue:expr $(=> $secondaryFieldKey:expr)?)?]
                // $(.$linkItem:ident $(-> <$linkItemTypeTo:ty>::[_ $(= $secondaryLinkKeyTo:expr)?])?
                //                    $(<- <$linkItemTypeFrom:ty>::[$secondaryLinkKeyFrom:expr])?)*
            $(::{ $($returnItem:ident),+})?
    ) => {..};
    (  // watch an item in the database
        $db:ident ==? <$itemType:ty>($keyValue:expr $(=> $secondaryFieldKey:expr)?)
            $(::{ $($returnItem:ident),+})?
    ) => {..};
    ( // watch items in the database
        $db:ident ==? <$itemType:ty>[$keyValue:expr $(=> $secondaryFieldKey:expr)?]
            $(::{ $($returnItem:ident),+})?
    ) => {..};
    // Create a new item in the database
    ( $db:ident <== $($itemToAdd:expr);+ ) => {..};
    // Delete an item from the database
    ( $db:ident <!= $($itemToDelete:ident),+ ) => {..};
    // Update an item in the database
    ( $db:ident =<= $($itemUpdated:expr => $itemOutdated:expr),+ ) => {..};
}

image (thanks too https://lukaslueg.github.io/macro_railroad_wasm_demo/)

this can be used like so:

// Create
use_db![db <== repo.clone(); doc.clone(); doc1.clone(); doc2.clone(); doc3.clone()]?;

// Read, (type annotations are optional and serve as documentation)
let read_repo: Repository = use_db![db ==> <Repository>::(1)]?;

let read_doc: Document = use_db![db ==> <Document>::("some name" => DocumentKey::name)]?;
let (id, repo_name): (Uuid, String) = use_db![db ==> <Repository>::(&repo.id)::{id, name} ]?;

let read_docs_ids: Vec<Document> = use_db![db ==> <Document>::[]]?;
let read_docs_ids: Vec<Uuid> = use_db![db ==> <Document>::[]::{id}]?;
let read_docs_ids: Vec<Uuid> = use_db![db ==> <Document>::[0..10]::{id}]?;
let read_docs_ids: Vec<String> = use_db![db ==> <Document>::["read"..="read9" => DocumentKey::name]::{name}]?;

// Update
use_db![db =<= doc_updated => doc.clone()]?;

// Delete
let removed: (Document, Repository) = use_db![db <!= doc, repo]?;

I was a bit constraint on some of the characters I was allowed too use but I like the end result. If you'd like to include this ill make a pull request with what I have so far implementation wise. Also let me know your opinion about the syntax. Thanks for your consideration. Love the project.

vincent-herlemont commented 1 month ago

@C4Phoenix Thank you for your message/work and support for the project! Your macro syntax you propose reduces the code. Are you already using this approach in your projects?


Regarding the integration into the native_db repository, for now, I will keep the crate more focused on a single query syntax. If it becomes relevant, why not fully adopt your approach? But for now, you are welcome to publish a crate that allows using native_db with a macro. You can include a link to it in the README.md of native_db.

C4Phoenix commented 1 month ago

@vincent-herlemont, I'm currently using it for one project but not yet extensively because adding the unit tests revealed the bug reported earlier. I really wanted to add a pattern to 'link' two keys together like in a graph database like surrealdb but I could not implement that properly (as I wanted to assume that a index was non unique). The current implementation also collects iterators often which might hide some ram costs in certain queries (when only selecting a value from a struct for instance). All that is too say still a bit of a work in progress but good enough for me, once the #208 is resolved and I've used/tested it more I'll look into publishing a crate. Thanks for the awesome work.