mirror of
https://github.com/salsa-rs/salsa.git
synced 2024-11-28 17:42:00 +00:00
document the database macro
This commit is contained in:
parent
4b0c8a6368
commit
ebb3769ad2
4 changed files with 124 additions and 9 deletions
|
@ -9,3 +9,4 @@
|
|||
- [YouTube videos](./videos.md)
|
||||
- [Plumbing](./plumbing.md)
|
||||
- [Query groups](./plumbing/query_groups.md)
|
||||
- [Database](./plumbing/database.md)
|
111
book/src/plumbing/database.md
Normal file
111
book/src/plumbing/database.md
Normal file
|
@ -0,0 +1,111 @@
|
|||
# Database
|
||||
|
||||
Continuing our dissection, the other thing which a user must define is a
|
||||
**database**, which looks something like this:
|
||||
|
||||
```rust,ignore
|
||||
{{#include ../../../examples/hello_world/main.rs:database}}
|
||||
```
|
||||
|
||||
The `salsa::database` procedural macro takes a list of query group
|
||||
structs (like `HelloWorldStorage`) and generates the following items:
|
||||
|
||||
* a copy of the database struct it is applied to
|
||||
* a struct `__SalsaDatabaseStorage` that contains all the storage structs for
|
||||
each query group. Note: these are the structs full of hashmaps etc that are
|
||||
generaetd by the query group procdural macro, not the `HelloWorldStorage`
|
||||
struct itself.
|
||||
* a struct `__SalsaDatabaseKey` that wraps an enum `__SalsaDatabaseKeyKind`. The
|
||||
enum contains one variant per query group, and in each variant contains the
|
||||
group key. This can be used to identify any query in the database.
|
||||
* an impl of `HasQueryGroup<G>` for each query group `G`
|
||||
* an impl of `salsa::plumbing::DatabaseStorageTypes` for the database struct
|
||||
* an impl of `salsa::plumbing::DatabaseOps` for the database struct
|
||||
* an impl of `salsa::plumbing::DatabaseKey<DB>` for the database struct `DB`
|
||||
|
||||
## Key constraint: we do not know the names of individual queries
|
||||
|
||||
There is one key constraint in the design here. None of this code knows the
|
||||
names of individual queries. It only knows the name of the query group storage
|
||||
struct. This means that we often delegate things to the group -- e.g., the
|
||||
database key is composed of group keys. This is similar to how none of the code
|
||||
in the query group knows the full set of query groups, and so it must use
|
||||
associated types from the `Database` trait whenever it needs to put something in
|
||||
a "global" context.
|
||||
|
||||
## The database storage struct
|
||||
|
||||
The `__SalsaDatabaseStorage` struct concatenates all of the query group storage
|
||||
structs. In the hello world example, it looks something like:
|
||||
|
||||
```rust
|
||||
struct __SalsaDatabaseStorage {
|
||||
hello_world: <HelloWorldStorage as salsa::plumbing::QueryGroup<DatabaseStruct>>::GroupStorage
|
||||
}
|
||||
```
|
||||
|
||||
## The database key struct / enum and the `DatabaseKey` impl
|
||||
|
||||
The `__SalsaDatabaseKey` and `__SalsaDatabaseKeyKind` types create a **database
|
||||
key**, which uniquely identifies any query in the database. It builds on the
|
||||
**group keys** created by the query groups, which uniquely identify a query
|
||||
within a given query group.
|
||||
|
||||
```rust
|
||||
struct __SalsaDatabaseKey {
|
||||
kind: __SalsaDatabaseKeyKind
|
||||
}
|
||||
|
||||
enum __SalsaDatabaseKeyKind {
|
||||
HelloWorld(
|
||||
<HelloWorldStorage as salsa::plumbing::QueryGroup<DatabaseStruct>>::GroupKey
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
We also generate an impl of `DatabaseKey`:
|
||||
|
||||
```rust,ignore
|
||||
{{#include ../../../components/salsa-macros/src/database_storage.rs:DatabaseKey}}
|
||||
```
|
||||
|
||||
## The `HasQueryGroup` impl
|
||||
|
||||
The `HasQueryGroup` trait allows a given query group to access its definition
|
||||
within the greater database. The impl is generated here:
|
||||
|
||||
```rust,ignore
|
||||
{{#include ../../../components/salsa-macros/src/database_storage.rs:HasQueryGroup}}
|
||||
```
|
||||
|
||||
and so for our example it would look something like
|
||||
|
||||
```rust
|
||||
impl salsa::plumbing::HasQueryGroup<HelloWorld> for DatabaseStruct {
|
||||
fn group_storage(&self) -> &HelloWorldStorage::GroupStorage {
|
||||
&self.hello_world
|
||||
}
|
||||
|
||||
fn database_key(group_key: HelloWorldStorage::GroupKey) -> __SalsaDatabaseKey {
|
||||
__SalsaDatabaseKey {
|
||||
kind: __SalsaDatabaseKeyKind::HelloWorld(group_key)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Other impls
|
||||
|
||||
Then there are a variety of other impls, like this one for `DatabaseStorageTypes`:
|
||||
|
||||
```rust,ignore
|
||||
{{#include ../../../components/salsa-macros/src/database_storage.rs:DatabaseStorageTypes}}
|
||||
```
|
||||
|
||||
Or this one for `DatabaseOps`, which defines the for-each method to
|
||||
invoke an operation on every kind of query in the database. It ultimately
|
||||
delegates to the `for_each` methods for the groups:
|
||||
|
||||
```rust,ignore
|
||||
{{#include ../../../components/salsa-macros/src/database_storage.rs:DatabaseOps}}
|
||||
```
|
|
@ -61,6 +61,7 @@ pub(crate) fn database(args: TokenStream, input: TokenStream) -> TokenStream {
|
|||
storage_fields.extend(quote! {
|
||||
#group_name_snake: #group_storage,
|
||||
});
|
||||
// ANCHOR:HasQueryGroup
|
||||
has_group_impls.extend(quote! {
|
||||
impl salsa::plumbing::HasQueryGroup<#group_path> for #database_name {
|
||||
fn group_storage(db: &Self) -> &#group_storage {
|
||||
|
@ -75,6 +76,7 @@ pub(crate) fn database(args: TokenStream, input: TokenStream) -> TokenStream {
|
|||
}
|
||||
}
|
||||
});
|
||||
// ANCHOR_END:HasQueryGroup
|
||||
}
|
||||
|
||||
// create group storage wrapper struct
|
||||
|
@ -122,7 +124,7 @@ pub(crate) fn database(args: TokenStream, input: TokenStream) -> TokenStream {
|
|||
});
|
||||
}
|
||||
|
||||
//
|
||||
// ANCHOR:DatabaseStorageTypes
|
||||
output.extend(quote! {
|
||||
impl salsa::plumbing::DatabaseStorageTypes for #database_name {
|
||||
type DatabaseKey = __SalsaDatabaseKey;
|
||||
|
@ -130,8 +132,9 @@ pub(crate) fn database(args: TokenStream, input: TokenStream) -> TokenStream {
|
|||
type DatabaseData = (#(#database_data),*);
|
||||
}
|
||||
});
|
||||
// ANCHOR_END:DatabaseStorageTypes
|
||||
|
||||
//
|
||||
// ANCHOR:DatabaseOps
|
||||
let mut for_each_ops = proc_macro2::TokenStream::new();
|
||||
for (QueryGroup { group_path }, group_storage) in
|
||||
query_groups.iter().zip(&query_group_storage_names)
|
||||
|
@ -152,11 +155,14 @@ pub(crate) fn database(args: TokenStream, input: TokenStream) -> TokenStream {
|
|||
}
|
||||
}
|
||||
});
|
||||
// ANCHOR_END:DatabaseOps
|
||||
|
||||
// ANCHOR:DatabaseKey
|
||||
output.extend(quote! {
|
||||
impl salsa::plumbing::DatabaseKey<#database_name> for __SalsaDatabaseKey {
|
||||
}
|
||||
});
|
||||
// ANCHOR_END:DatabaseKey
|
||||
|
||||
output.extend(has_group_impls);
|
||||
|
||||
|
@ -189,12 +195,7 @@ struct QueryGroup {
|
|||
impl QueryGroup {
|
||||
/// The name of the query group trait.
|
||||
fn name(&self) -> Ident {
|
||||
self.group_path
|
||||
.segments
|
||||
.last()
|
||||
.unwrap()
|
||||
.ident
|
||||
.clone()
|
||||
self.group_path.segments.last().unwrap().ident.clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -73,6 +73,7 @@ fn length(db: &impl HelloWorld, (): ()) -> usize {
|
|||
// The database struct can contain basically anything you need, but it
|
||||
// must have a `runtime` field as shown, and you must implement the
|
||||
// `salsa::Database` trait (as shown below).
|
||||
// ANCHOR:database
|
||||
#[salsa::database(HelloWorldStorage)]
|
||||
#[derive(Default)]
|
||||
struct DatabaseStruct {
|
||||
|
@ -89,6 +90,7 @@ impl salsa::Database for DatabaseStruct {
|
|||
&mut self.runtime
|
||||
}
|
||||
}
|
||||
// ANCHOR_END:database
|
||||
|
||||
// This shows how to use a query.
|
||||
fn main() {
|
||||
|
|
Loading…
Reference in a new issue