document the database macro

This commit is contained in:
Niko Matsakis 2020-06-26 22:19:30 +00:00
parent 4b0c8a6368
commit ebb3769ad2
4 changed files with 124 additions and 9 deletions

View file

@ -9,3 +9,4 @@
- [YouTube videos](./videos.md)
- [Plumbing](./plumbing.md)
- [Query groups](./plumbing/query_groups.md)
- [Database](./plumbing/database.md)

View 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}}
```

View file

@ -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()
}
}

View file

@ -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() {