use std::collections::HashMap;
use proc_macro::TokenStream;
use quote::quote;
use syn::Result;
use syn::*;
use super::*;
pub fn derive(input: DeriveInput) -> Result<TokenStream> {
let crate_name = crate_name();
let fields = parse_struct_and_fields(&input, "AnonDB")?;
let mut field_primary_keys = HashMap::<Ident, IndexDef>::default();
let mut field_indices = HashMap::<Ident, Vec<IndexDef>>::default();
let mut field_doc_generic = HashMap::<Ident, Type>::default();
for field in fields {
let (primary_key, indices) = parse_attributes(field)?;
let field_ident = field.ident.clone().expect("expected field ident to exist");
field_primary_keys.insert(field_ident.clone(), primary_key);
field_indices.insert(field_ident.clone(), indices);
let doc_generic = get_first_generic(&field.ty).unwrap();
field_doc_generic.insert(field_ident.clone(), doc_generic.clone());
}
for (_collection_name, indices) in &field_indices {
for index in indices {
for (option_name, _) in &index.options {
if option_name.to_string() == "primary" {
return Err(Error::new_spanned(
option_name,
format!("Custom indices may not be primary. Use the primary_key attribute instead."),
));
}
}
}
}
let assign_collection_vars = fields.iter().map(|f| {
let field_name = f.ident.clone().unwrap();
let doc_generic = field_doc_generic.get(&field_name).expect("expected field document type to be known");
let primary_key_parts = field_primary_keys.get(&field_name).unwrap();
let primary_key_fields = primary_key_parts.fields.iter().map(|v| v.name.clone()).collect::<Vec<_>>();
let mut all_indexed_fields = HashMap::<Ident, ()>::default();
for index in field_indices.get(&field_name).cloned().unwrap_or_default() {
for field in index.fields {
all_indexed_fields.insert(field.name, ());
}
}
let field_extractors = all_indexed_fields.iter().map(|(k, _)| {
quote! {
if let Some(v) = query.#k.as_ref() {
out.insert(stringify!(#k).to_string(), v.into());
}
}
});
let extract_index_fields = quote! {
{
fn extractor(query: & <#doc_generic as #crate_name::Queryable> ::DocumentQuery) -> std::collections::HashMap<String, #crate_name::Param> {
let mut out = std::collections::HashMap::default();
#(#field_extractors)*
out
}
self.#field_name.set_field_extractor(extractor);
}
};
let index_assignments = field_indices
.get(&field_name)
.cloned()
.unwrap_or_default()
.into_iter()
.map(|index| {
let fields = index
.fields
.iter()
.map(|i| i.name.clone())
.collect::<Vec<_>>();
let options = index.options.iter().map(|(k, v)| quote! { #k: #v }).collect::<Vec<_>>();
quote! {
self.#field_name.add_index(
#crate_name::Index {
collection_name: stringify!(#field_name).into(),
field_names: vec![
#(
(
stringify!(#fields).to_string(),
<<#doc_generic as #crate_name::Queryable>::DocumentPhantom>::#fields ().stats()
),
)*
],
serialize: |doc: &#doc_generic| -> Vec<u8> {
let mut key = #crate_name::anondb_kv::LexicographicKey::default();
#({
let bytes = <_ as #crate_name::anondb_kv::SerializeLexicographic>::serialize_lex(&doc.#fields);
key.append_key_slice(bytes.as_slice());
})*
key.take()
},
options: #crate_name::IndexOptions {
#(#options,)*
..Default::default()
}
}
)?;
}
});
quote! {
{
use #crate_name::SerializeLexicographic;
self.#field_name.set_kv(kv.clone())?;
self.#field_name.set_name(stringify!(#field_name).into())?;
self.#field_name.set_primary_key((
vec![#(
(
stringify!(#primary_key_fields).to_string(),
<<#doc_generic as #crate_name::Queryable>::DocumentPhantom>::#primary_key_fields ().stats()
)
),*], |doc: &#doc_generic| -> Vec<u8> {
let mut key = #crate_name::anondb_kv::LexicographicKey::default();
#(
let bytes = <_ as #crate_name::anondb_kv::SerializeLexicographic>::serialize_lex(&doc.#primary_key_fields);
key.append_key_slice(bytes.as_slice());
)*
key.take()
}))?;
#extract_index_fields
#(#index_assignments)*
}
}
});
let collection_checks = fields.iter().map(|f| {
let field_name = &f.ident;
quote! {
for table_name in self.#field_name.table_names() {
if let Some(collection) = all_table_names.get(&table_name) {
#crate_name::anyhow::bail!("AnonDB: invalid configuration. Table name \"{}\" is used by two different collections: \"{}\" and \"{}\"", table_name, collection, stringify!(#field_name));
}
all_table_names.insert(table_name.into(), stringify!(#field_name).into());
}
self.#field_name.construct_indices()?;
if !self.#field_name.has_primary_key() {
#crate_name::anyhow::bail!("Collection \"{}\" does not have a primary key defined!", self.#field_name.name());
}
}
});
let defaults = fields.iter().map(|f| {
let field_name = &f.ident;
quote! {
#field_name: #crate_name::Collection::default(),
}
});
let name = &input.ident;
let kv_generic_name = get_kv_generic(&input)?;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let expanded = quote! {
impl #impl_generics Default for #name #ty_generics #where_clause {
fn default() -> Self {
Self {
#(#defaults)*
}
}
}
impl #impl_generics #name #ty_generics #where_clause {
pub fn in_memory(bytes_maybe: Option<&[u8]>) -> #crate_name::anyhow::Result<::std::sync::Arc<Self>> {
let mut s = Self::default();
let kv = ::std::sync::Arc::new(#kv_generic_name::in_memory(bytes_maybe)?);
s.setup(kv)?;
Ok(::std::sync::Arc::new(s))
}
pub fn at_path(path: &::std::path::Path) -> #crate_name::anyhow::Result<::std::sync::Arc<Self>> {
let mut s = Self::default();
let kv = ::std::sync::Arc::new(#kv_generic_name::at_path(path)?);
s.setup(kv)?;
Ok(::std::sync::Arc::new(s))
}
fn setup(&mut self, kv: ::std::sync::Arc<#kv_generic_name>) -> #crate_name::anyhow::Result<()> {
#(#assign_collection_vars)*
let mut all_table_names = ::std::collections::HashMap::<String, String>::default();
#(#collection_checks)*
Ok(())
}
}
};
Ok(TokenStream::from(expanded))
}
fn parse_attributes(field: &Field) -> Result<(IndexDef, Vec<IndexDef>)> {
let mut primary_key_maybe: Option<IndexDef> = None;
let mut indices = Vec::default();
for attr in &field.attrs {
if !attr.path().is_ident("anondb") {
continue;
}
let index_def = attr.parse_args::<IndexDef>()?;
match index_def.keyword.to_string().as_str() {
"primary_key" => {
if !index_def.options.is_empty() {
return Err(Error::new_spanned(
attr,
format!("AnonDB primary_key attribute does not support options"),
));
}
primary_key_maybe = Some(index_def);
}
"index" => {
indices.push(index_def);
}
key => {
return Err(Error::new_spanned(
attr,
format!(
"AnonDB unrecognized key \"{key}\" in attribute for field \"{}\"",
stringify!(field)
),
));
}
}
}
if primary_key_maybe.is_none() {
return Err(Error::new_spanned(
field,
format!(
"AnonDB collection \"{}\" does not have a primary key specified. You may do so with #[anondb(primary_key = field_name)]",
field.ident.clone().unwrap().to_string()
),
));
}
Ok((primary_key_maybe.unwrap(), indices))
}
fn get_kv_generic(input: &DeriveInput) -> Result<&Ident> {
let generic = match input.generics.params.first() {
Some(generic) => generic,
None => {
return Err(Error::new_spanned(
input,
"AnonDB only works with structs that have exactly 1 generic argument, which is trait bounded to anondb_kv::KV",
));
}
};
let kv_generic_name = match generic {
GenericParam::Type(type_param) => {
let generic_name = &type_param.ident;
if let Some(_bound) = type_param.bounds.first() {
} else {
return Err(Error::new_spanned(
type_param,
"AnonDB struct generic must have a first trait bound that is anondb_kv::KV",
));
}
generic_name
}
GenericParam::Const(v) => {
return Err(Error::new_spanned(
v,
"AnonDB struct first generic must be a type, got a const",
));
}
GenericParam::Lifetime(v) => {
return Err(Error::new_spanned(
v,
"AnonDB struct first generic must be a type, got a lifetime",
));
}
};
Ok(kv_generic_name)
}
fn get_first_generic(ty: &Type) -> Option<&Type> {
if let Type::Path(type_path) = ty {
if let Some(segment) = type_path.path.segments.last() {
if let PathArguments::AngleBracketed(args) = &segment.arguments {
if let Some(first_arg) = args.args.first() {
if let GenericArgument::Type(ty) = first_arg {
return Some(ty);
}
}
}
}
}
None
}