Browse Source

Initial reworking with simple DB struct walking implemented.

Kestrel 1 year ago
parent
commit
118400a12a

+ 48 - 0
microrm-macros/src/database.rs

@@ -0,0 +1,48 @@
+use convert_case::{Case, Casing};
+use quote::{quote, format_ident};
+
+pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let input : syn::DeriveInput = syn::parse_macro_input!(tokens);
+
+    let items = if let syn::Data::Struct(syn::DataStruct { struct_token: _, fields: syn::Fields::Named(fields), semi_token: _ } ) = input.data {
+        fields.named.into_iter().map(|f| {
+            (f.ident.unwrap(), f.ty)
+        }).collect::<Vec<_>>()
+    }
+    else {
+        panic!("Can only derive Database on data structs with named fields!");
+    };
+
+    println!("items: {:?}", &items);
+
+    let db_ident = input.ident;
+
+    let items = items.iter().map(|field| {
+        let item_combined_name = format_ident!("{}{}", db_ident, field.0.to_string().to_case(Case::Snake));
+        let item_base_name = &field.0.to_string();
+        let item_type = &field.1;
+
+        quote! {
+            struct #item_combined_name;
+            impl crate::db::DatabaseItem for #item_combined_name {
+                fn item_key() -> &'static str { #item_base_name }
+                fn dependency_keys() -> &'static [&'static str] { &[] } // TODO
+
+                fn accept_entity_visitor(visitor: &mut impl crate::entity::EntityVisitor) {
+                    <#item_type as crate::db::DatabaseSpec>::accept_entity_visitor(visitor);
+                }
+            }
+            v.visit::<#item_combined_name>();
+        }
+    });
+
+    let out = quote! {
+        impl crate::db::Database for #db_ident {
+            fn accept_item_visitor(v: &mut impl crate::db::DatabaseItemVisitor) {
+                #(#items)*
+            }
+        }
+    }.into();
+    println!("out: {}", out);
+    out
+}

+ 32 - 267
microrm-macros/src/entity.rs

@@ -1,281 +1,46 @@
-use proc_macro::TokenStream;
-use quote::{format_ident, quote};
-use syn::{parse_macro_input, DeriveInput};
-
 use convert_case::{Case, Casing};
+use quote::{quote, format_ident};
 
-fn parse_fk(attrs: &[syn::Attribute]) -> bool {
-    for attr in attrs {
-        if attr.path.segments.len() == 1
-            && attr.path.segments.last().unwrap().ident == "microrm_foreign"
-        {
-            return true;
-        }
-    }
-
-    false
-}
-
-fn derive_columns<'a, I: Iterator<Item = &'a syn::Field>>(
-    input: &DeriveInput,
-    microrm_ref: &proc_macro2::TokenStream,
-    fields: I,
-) -> proc_macro2::TokenStream {
-    let struct_name = &input.ident;
-    let columns_name = format_ident!("_{}_columns", &input.ident.to_string().to_case(Case::Snake));
-    let mut index = 0usize;
-
-    let mut column_types = Vec::new();
-    let mut column_impls = Vec::new();
-    let mut column_consts = Vec::new();
-    let mut column_array = Vec::new();
-
-    for name in fields {
-        let original_case = name.ident.as_ref().unwrap().clone();
-        let snake_case = original_case.to_string();
-        if snake_case != snake_case.to_case(Case::Snake) {
-            return quote::quote_spanned!(original_case.span() => compile_error!("Names must be in snake_case"));
-        }
-
-        let converted_case =
-            format_ident!("{}", original_case.to_string().to_case(Case::UpperCamel));
-
-        let ty = &name.ty;
-
-        let mut fk_table_name = quote! { None };
-        let mut fk_column_name = quote! { None };
-
-        if parse_fk(&name.attrs) {
-            fk_table_name = quote! { Some(<<#ty as #microrm_ref::entity::EntityID>::Entity as #microrm_ref::entity::Entity>::table_name()) };
-            fk_column_name = quote! { Some("id") };
-        }
-
-        index += 1;
-        column_types.push(quote! { pub struct #converted_case (); });
-        column_impls.push(quote! {
-            impl #microrm_ref::entity::EntityColumn for #columns_name::#converted_case {
-                type Entity = #struct_name;
-
-                fn sql_type(&self) -> &'static str { <#ty as #microrm_ref::model::Modelable>::column_type() }
-                fn index(&self) -> usize { #index }
-                fn name(&self) -> &'static str { #snake_case }
-
-                fn fk_table_name(&self) -> Option<&'static str> { #fk_table_name }
-                fn fk_column_name(&self) -> Option<&'static str> { #fk_column_name }
-            }
-        });
-        column_consts.push(quote! {
-            #[allow(non_upper_case_globals)]
-            pub const #converted_case : #columns_name::#converted_case = #columns_name::#converted_case();
-        });
-
-        column_array.push(quote! { & #columns_name::#converted_case() });
-    }
-
-    let columns_array_name = format_ident!(
-        "{}_COLUMNS",
-        struct_name.to_string().to_case(Case::ScreamingSnake)
-    );
-
-    quote! {
-        pub mod #columns_name {
-            pub struct ID ();
-            #(#column_types)*
-        }
-
-        impl #microrm_ref::entity::EntityColumn for #columns_name::ID {
-            type Entity = #struct_name;
-            fn sql_type(&self) -> &'static str { "integer" }
-            fn index(&self) -> usize { 0 }
-            fn name(&self) -> &'static str { "id" }
-
-            fn fk_table_name(&self) -> Option<&'static str> { None }
-            fn fk_column_name(&self) -> Option<&'static str> { None }
-        }
-        #(#column_impls)*
-
-        impl #struct_name {
-            pub const ID : #columns_name::ID = #columns_name::ID();
-            #(#column_consts)*
-        }
-
-        #microrm_ref::re_export::lazy_static::lazy_static!{
-            static ref #columns_array_name: [&'static dyn #microrm_ref::entity::EntityColumn<Entity = #struct_name>;#index + 1] = {
-                [ &#columns_name::ID(), #(#column_array),* ]
-            };
-        }
-    }
-}
-
-fn derive_id(
-    input: &DeriveInput,
-    microrm_ref: &proc_macro2::TokenStream,
-) -> proc_macro2::TokenStream {
-    let struct_name = &input.ident;
-    let id_name = format_ident!("{}ID", &input.ident);
-    let id_name_as_str = id_name.to_string();
-
-    quote! {
-        #[derive(Debug,PartialEq,Clone,Copy,#microrm_ref::re_export::serde::Serialize,#microrm_ref::re_export::serde::Deserialize)]
-        #[allow(unused)]
-        pub struct #id_name (i64);
-
-        impl #microrm_ref::entity::EntityID for #id_name {
-            type Entity = #struct_name;
-            fn from_raw_id(raw: i64) -> Self { Self(raw) }
-            fn raw_id(&self) -> i64 { self.0 }
-        }
-
-        impl std::fmt::Display for #id_name {
-            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-                f.write_fmt(format_args!("{}({})", #id_name_as_str, self.0))
-            }
-        }
+pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let input : syn::DeriveInput = syn::parse_macro_input!(tokens);
 
-        impl #microrm_ref::model::Modelable for #id_name {
-            fn bind_to(&self, stmt: &mut #microrm_ref::re_export::sqlite::Statement, col: usize) -> #microrm_ref::re_export::sqlite::Result<()> {
-                use #microrm_ref::re_export::sqlite::Bindable;
-                self.0.bind(stmt, col)
-            }
-            fn build_from(stmt: &#microrm_ref::re_export::sqlite::Statement, col_offset: usize) -> #microrm_ref::re_export::sqlite::Result<(Self, usize)> where Self: Sized {
-                stmt.read::<i64>(col_offset).map(|x| (#id_name(x), 1))
-            }
-            fn column_type() -> &'static str where Self: Sized {
-                "integer"
-            }
-        }
+    let parts = if let syn::Data::Struct(syn::DataStruct { struct_token: _, fields: syn::Fields::Named(fields), semi_token: _ } ) = input.data {
+        fields.named.into_iter().map(|f| {
+            (f.ident.unwrap(), f.ty)
+        }).collect::<Vec<_>>()
     }
-}
-
-pub(crate) fn derive(tokens: TokenStream) -> TokenStream {
-    let input = parse_macro_input!(tokens as DeriveInput);
-
-    let microrm_ref = crate::parse_microrm_ref(&input.attrs);
-
-    let struct_name = &input.ident;
-    let id_name = format_ident!("{}ID", &input.ident);
-
-    let table_name = format!("{}", struct_name).to_case(Case::Snake);
-
-    let st = match &input.data {
-        syn::Data::Struct(st) => st,
-        _ => panic!("Can only use derive(Entity) on structs!"),
-    };
-    let fields = match &st.fields {
-        syn::Fields::Named(fields) => fields,
-        _ => panic!("Can only use derive(Entity) on non-unit structs with named fields!"),
+    else {
+        panic!("Can only derive Entity on data structs with named fields!");
     };
 
-    let mut variants = Vec::new();
-    let mut value_references = Vec::new();
-    let mut build_clauses = Vec::new();
-
-    // let mut foreign_keys = Vec::new();
-    // let mut foreign_key_impls = Vec::new();
-
-    let mut index: usize = 0;
-
-    let column_output = derive_columns(&input, &microrm_ref, fields.named.iter());
-    let id_output = derive_id(&input, &microrm_ref);
-
-    for name in fields.named.iter() {
-        let original_case = name.ident.as_ref().unwrap().clone();
-        let snake_case = original_case.to_string().to_case(Case::Snake);
-
-        if original_case != snake_case {
-            return quote::quote_spanned!(original_case.span() => compile_error!("Names must be in snake_case")).into();
-        }
-
-        let converted_case =
-            format_ident!("{}", original_case.to_string().to_case(Case::UpperCamel));
-        variants.push(converted_case.clone());
-
-        let field_name = name.ident.as_ref().unwrap().clone();
-        value_references.push(quote! { &self. #field_name });
-
-        let ty = &name.ty;
-
-        index += 1;
-        build_clauses.push(quote! { #field_name: <#ty as #microrm_ref::model::Modelable>::build_from(stmt, #index)?.0 });
-
-        /*
-        if parse_fk(&name.attrs) {
-            let fk_struct_name = format_ident!("{}{}ForeignKey", struct_name, converted_case);
-            let ty = &name.ty;
-            foreign_keys.push(quote! {
-                &#fk_struct_name { col: #columns_name::#converted_case }
-            });
-            foreign_key_impls.push(quote!{
-                struct #fk_struct_name {
-                    col: #columns_name
-                }
-                impl #microrm_ref::entity::EntityForeignKey<#columns_name> for #fk_struct_name {
-                    /*
-                    fn local_column(&self) -> &#columns_name { &self.col }
-                    fn foreign_table_name(&self) -> &'static str {
-                        <<#ty as #microrm_ref::entity::EntityID>::Entity as #microrm_ref::entity::Entity>::table_name()
-                    }
-                    fn foreign_column_name(&self) -> &'static str {
-                        "id"
-                    }
-                    */
+    let entity_ident = input.ident;
+
+    let part_bodies = parts.iter().map(|part| {
+        let part_combined_name = format_ident!("{}{}", entity_ident, part.0.to_string().to_case(Case::Snake));
+        let part_base_name = &part.0.to_string();
+        let part_type = &part.1;
+        quote! {
+            struct #part_combined_name;
+            impl crate::entity::EntityPart for #part_combined_name {
+                type Datum = #part_type;
+                fn part_name() -> &'static str {
+                    #part_base_name
                 }
-            });
-        }
-        */
-    }
-
-    let columns_array_name = format_ident!(
-        "{}_COLUMNS",
-        struct_name.to_string().to_case(Case::ScreamingSnake)
-    );
-
-    let field_count = fields.named.iter().count();
-
-    let columns_name = format_ident!("_{}_columns", &input.ident.to_string().to_case(Case::Snake));
-
-    quote!{
-        // Related types for #struct_name
-
-        #column_output
-        #id_output
-
-        // Implementations for #struct_name
-        impl #microrm_ref::entity::Entity for #struct_name {
-            type ID = #id_name;
-            type IDColumn = #columns_name::ID;
-
-            fn table_name() -> &'static str { #table_name }
-            fn column_count() -> usize {
-                // +1 for ID column
-                #field_count + 1
-            }
-            fn visit_values<E, F: FnMut(&dyn #microrm_ref::model::Modelable) -> (Result<(), E>)>(&self, visit: &mut F) -> Result<(),E> {
-                #(visit(#value_references)?);*;
-
-                Ok(())
-            }
-
-            fn build_from(stmt: &#microrm_ref::re_export::sqlite::Statement) -> #microrm_ref::re_export::sqlite::Result<Self> {
-                Ok(Self {
-                    #(#build_clauses),*
-                })
             }
+            v.visit::<#part_combined_name>();
+        }
+    });
 
-            fn columns() -> &'static [&'static dyn #microrm_ref::entity::EntityColumn<Entity = Self>] {
-                #columns_array_name.as_ref()
-            }
+    let entity_name = entity_ident.to_string().to_case(Case::Snake);
 
-            fn id_column() -> Self::IDColumn {
-                Self::ID
+    quote! {
+        impl crate::entity::Entity for #entity_ident {
+            fn entity_name() -> &'static str { #entity_name }
+            fn accept_part_visitor(v: &mut impl crate::entity::EntityPartVisitor) {
+                #(
+                    #part_bodies
+                );*
             }
-
-            /*fn foreign_keys() -> &'static [&'static dyn #microrm_ref::entity::EntityForeignKey<Self::Column>] {
-                &[#(#foreign_keys),*]
-            }*/
         }
-
-        // Foreign key struct implementations
-        // #(#foreign_key_impls)*
     }.into()
 }

+ 0 - 77
microrm-macros/src/index.rs

@@ -1,77 +0,0 @@
-use proc_macro::TokenStream;
-use quote::{format_ident, quote};
-use syn::parse_macro_input;
-
-use convert_case::{Case, Casing};
-
-type ColumnList = syn::punctuated::Punctuated<syn::ExprPath, syn::Token![,]>;
-struct MakeIndexParams {
-    unique: Option<syn::Token![!]>,
-    name: syn::Ident,
-    #[allow(dead_code)]
-    comma: syn::Token![,],
-    columns: ColumnList,
-}
-
-impl syn::parse::Parse for MakeIndexParams {
-    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
-        Ok(Self {
-            unique: input.parse()?,
-            name: input.parse()?,
-            comma: input.parse()?,
-            columns: ColumnList::parse_separated_nonempty(input)?,
-        })
-    }
-}
-
-pub(crate) fn do_make_index(
-    tokens: TokenStream,
-    microrm_ref: proc_macro2::TokenStream,
-) -> TokenStream {
-    let input = parse_macro_input!(tokens as MakeIndexParams);
-
-    let index_struct_name = input.name;
-    let index_sql_name = format!("{}", index_struct_name).to_case(Case::Snake);
-
-    let columns = input.columns.clone().into_iter();
-
-    let unique = input.unique.is_some();
-
-    let first_column = columns.clone().next().unwrap();
-
-    let columns_array_name = format_ident!(
-        "INDEX_COLUMN_NAMES_{}",
-        index_struct_name.to_string().to_case(Case::ScreamingSnake)
-    );
-    let column_count = columns.len();
-
-    quote! {
-        pub struct #index_struct_name ();
-
-        #microrm_ref::re_export::lazy_static::lazy_static!{
-            static ref #columns_array_name : [&'static str; #column_count] = {
-                use #microrm_ref::entity::EntityColumn;
-                [
-                    #( #columns . name() ),*
-                ]
-            };
-        }
-
-        impl #microrm_ref::entity::Index for #index_struct_name {
-            fn index_name() -> &'static str {
-                #index_sql_name
-            }
-            fn table_name() -> &'static str {
-                use #microrm_ref::entity::EntityColumn;
-                #first_column.table_name()
-            }
-            fn column_names() -> &'static [&'static str] {
-                #columns_array_name.as_ref()
-            }
-            fn unique() -> bool where Self: Sized {
-                #unique
-            }
-        }
-    }
-    .into()
-}

+ 17 - 1
microrm-macros/src/lib.rs

@@ -1,5 +1,19 @@
 use proc_macro::TokenStream;
-use quote::quote;
+
+mod database;
+mod entity;
+
+#[proc_macro_derive(Entity)]
+pub fn derive_entity(tokens: TokenStream) -> TokenStream {
+    entity::derive(tokens)
+}
+
+#[proc_macro_derive(Database)]
+pub fn derive_database(tokens: TokenStream) -> TokenStream {
+    database::derive(tokens)
+}
+
+/*
 
 mod entity;
 mod index;
@@ -85,3 +99,5 @@ pub fn query(tokens: TokenStream) -> TokenStream {
     query::do_query(tokens)
 }
 */
+
+*/

+ 0 - 130
microrm-macros/src/modelable.rs

@@ -1,130 +0,0 @@
-use proc_macro::TokenStream;
-use quote::quote;
-use syn::{parse_macro_input, DeriveInput};
-
-pub fn derive(tokens: TokenStream) -> TokenStream {
-    let input = parse_macro_input!(tokens as DeriveInput);
-
-    if let syn::Data::Enum(e) = &input.data {
-        return derive_modelable_enum(&input, e);
-    }
-
-    if let syn::Data::Struct(s) = &input.data {
-        if s.fields.len() == 1 {
-            return derive_transparent_struct(&input, s);
-        }
-    }
-
-    derive_modelable_general(&input)
-}
-
-fn derive_transparent_struct(input: &syn::DeriveInput, ds: &syn::DataStruct) -> TokenStream {
-    // for single-element structs, we can simply store these transparently however the element
-    // would be stored
-    let microrm_ref = crate::parse_microrm_ref(&input.attrs);
-    let struct_name = &input.ident;
-
-    let field = ds.fields.iter().next().unwrap();
-
-    let (bind_to, build_from) = if let Some(i) = &field.ident {
-        (
-            quote! { self.#i.bind_to(stmt, col) },
-            quote! { Self { #i: field.0 } },
-        )
-    } else {
-        (
-            quote! { self.0.bind_to(stmt, col) },
-            quote! { Self(field.0) },
-        )
-    };
-
-    let field_ty = &field.ty;
-
-    quote! {
-        impl #microrm_ref::model::Modelable for #struct_name {
-            fn bind_to(&self, stmt: &mut #microrm_ref::re_export::sqlite::Statement, col: usize) -> #microrm_ref::re_export::sqlite::Result<()> {
-                #bind_to
-            }
-
-            fn build_from(stmt: &#microrm_ref::re_export::sqlite::Statement, col_offset: usize) -> #microrm_ref::re_export::sqlite::Result<(Self,usize)> {
-                let field = #field_ty::build_from(stmt, col_offset)?;
-                Ok((#build_from, field.1))
-            }
-
-            fn column_type() -> &'static str where Self: Sized {
-                <#field_ty as #microrm_ref::model::Modelable>::column_type()
-            }
-        }
-    }.into()
-}
-
-fn derive_modelable_enum(input: &syn::DeriveInput, de: &syn::DataEnum) -> TokenStream {
-    for variant in &de.variants {
-        if !variant.fields.is_empty() {
-            return derive_modelable_general(input);
-        }
-    }
-
-    // only unit variants! we can store as a string
-    let microrm_ref = crate::parse_microrm_ref(&input.attrs);
-    let enum_name = &input.ident;
-
-    let mut variant_names = Vec::new();
-    let mut variant_name_strs = Vec::new();
-
-    for variant in &de.variants {
-        variant_names.push(variant.ident.clone());
-        variant_name_strs.push(variant.ident.to_string());
-    }
-
-    quote! {
-        impl #microrm_ref::model::Modelable for #enum_name {
-            fn bind_to(&self, stmt: &mut #microrm_ref::re_export::sqlite::Statement, col: usize) -> #microrm_ref::re_export::sqlite::Result<()> {
-                match self {
-                    #(
-                        Self::#variant_names => #variant_name_strs.bind_to(stmt, col)
-                    ),*
-                }
-            }
-
-            fn build_from(stmt: &#microrm_ref::re_export::sqlite::Statement, col_offset: usize) -> #microrm_ref::re_export::sqlite::Result<(Self,usize)> {
-                let str_form = String::build_from(stmt, col_offset)?.0;
-                #(
-                    if str_form == #variant_name_strs { return Ok((Self::#variant_names, 1)) }
-                )*
-
-                return Err(#microrm_ref::re_export::sqlite::Error { code: None, message: None })
-            }
-
-            fn column_type() -> &'static str where Self: Sized {
-                "text"
-            }
-        }
-    }.into()
-}
-
-fn derive_modelable_general(input: &syn::DeriveInput) -> TokenStream {
-    let microrm_ref = crate::parse_microrm_ref(&input.attrs);
-
-    let ident = &input.ident;
-
-    quote!{
-        impl #microrm_ref::model::Modelable for #ident {
-            fn bind_to(&self, stmt: &mut #microrm_ref::re_export::sqlite::Statement, col: usize) -> #microrm_ref::re_export::sqlite::Result<()> {
-                use #microrm_ref::re_export::sqlite;
-                use #microrm_ref::model::Modelable;
-                serde_json::to_string(self).expect("can be serialized").bind_to(stmt, col)
-            }
-            fn build_from(stmt: &#microrm_ref::re_export::sqlite::Statement, col_offset: usize) -> #microrm_ref::re_export::sqlite::Result<(Self,usize)> {
-                use #microrm_ref::re_export::sqlite;
-                use #microrm_ref::model::Modelable;
-                let str_data = stmt.read::<String>(col_offset).map_err(|e| sqlite::Error { code: None, message: Some(e.to_string()) })?;
-                let data = serde_json::from_str(str_data.as_str()).map_err(|e| sqlite::Error { code: None, message: Some(e.to_string()) })?;
-                Ok((data,1))
-            }
-            fn column_type() -> &'static str where Self: Sized {
-                "blob"
-            }
-        }
-    }.into()
-}

+ 0 - 83
microrm-macros/src/query.rs

@@ -1,83 +0,0 @@
-use proc_macro::TokenStream;
-use quote::{format_ident, quote};
-use syn::{parse_macro_input, DeriveInput};
-use syn::parse::{Parse,ParseStream};
-
-use std::rc::Rc;
-
-/*
-    SELECT [(Column, Column, ...)] FROM Type
-        [WHERE (Column = NAMED_PLACEHOLDER)]
-*/
-
-mod kw {
-    syn::custom_keyword!(SELECT);
-    syn::custom_keyword!(FROM);
-    syn::custom_keyword!(WHERE);
-}
-
-enum Expr {
-    Column(syn::Ident),
-    Constant,
-    Placeholder(&'static str),
-    And(Rc<Expr>, Rc<Expr>),
-    Or(Rc<Expr>, Rc<Expr>),
-    Equal(Rc<Expr>, Rc<Expr>),
-}
-
-impl Parse for Expr {
-    fn parse(input: ParseStream) -> syn::Result<Self> {
-        // input.peek(syn::Ident)
-        todo!()
-    }
-}
-
-type ColumnList = syn::punctuated::Punctuated<syn::Ident, syn::Token![,]>;
-
-struct Select {
-    select_token: kw::SELECT,
-    column_list: Option<ColumnList>,
-    from_token: kw::FROM,
-    what: syn::TypePath,
-    where_token: Option<kw::WHERE>,
-    where_clause: Option<Expr>
-}
-
-impl Parse for Select {
-    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
-        let mut where_token = None;
-        Ok(Self {
-            select_token: input.parse()?,
-            column_list: {
-                if input.peek(kw::FROM) {
-                    None
-                }
-                else {
-                    Some(ColumnList::parse_separated_nonempty(input)?)
-                }
-            },
-            from_token: input.parse()?,
-            what: input.parse()?,
-            where_token: {
-                where_token = input.parse()?;
-                where_token
-            },
-            where_clause: {
-                if where_token.is_some() {
-                    Some(input.parse()?)
-                }
-                else {
-                    None
-                }
-            }
-        })
-    }
-}
-
-pub fn do_query(tokens: TokenStream) -> TokenStream {
-    let input = parse_macro_input!(tokens as Select);
-
-    quote!{
-        
-    }.into()
-}

+ 3 - 3
microrm/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "microrm"
-version = "0.3.10"
+version = "0.4.0"
 edition = "2021"
 license = "BSD-4-Clause"
 authors = ["Kestrel <kestrel@flying-kestrel.ca>"]
@@ -12,7 +12,7 @@ description = "Lightweight ORM using sqlite as a backend."
 [dependencies]
 base64 = "0.13"
 sha2 = "0.10"
-sqlite = "0.26"
+sqlite = "0.33"
 serde = { version = "1.0", features = ["derive"] }
 serde_bytes = { version = "0.11.6" }
 serde_json = { version = "1.0" }
@@ -22,7 +22,7 @@ microrm-macros = { path = "../microrm-macros", version = "0.2.5" }
 log = "0.4.17"
 
 [dev-dependencies]
-criterion = "0.3"
+criterion = "0.5"
 rand = "0.8.5"
 stats_alloc = "0.1.10"
 async-std = "1.11"

+ 180 - 0
microrm/src/db.rs

@@ -0,0 +1,180 @@
+use crate::entity::{Entity, EntityDatum, EntityVisitor};
+
+pub struct Unique<T: EntityDatum> {
+    _ghost: std::marker::PhantomData<T>,
+}
+
+impl<T: EntityDatum> EntityDatum for Unique<T> {
+    
+}
+
+pub struct IDMap<T: Entity> {
+    conn: std::cell::OnceCell<std::sync::Arc<sqlite::ConnectionThreadSafe>>,
+    _ghost: std::marker::PhantomData<T>,
+}
+
+pub struct AssocSet<T: Entity> {
+    conn: std::cell::OnceCell<std::sync::Arc<sqlite::ConnectionThreadSafe>>,
+    _ghost: std::marker::PhantomData<T>,
+}
+
+pub struct Index<T: Entity, Key: EntityDatum> {
+    conn: std::cell::OnceCell<std::sync::Arc<sqlite::ConnectionThreadSafe>>,
+    _ghost: std::marker::PhantomData<(T,Key)>,
+}
+
+pub trait DatabaseItem {
+    fn item_key() -> &'static str;
+    fn dependency_keys() -> &'static [&'static str];
+
+    fn is_index() -> bool { false }
+    fn index_over() -> &'static str { unreachable!() }
+    fn index_columns() -> &'static [&'static str] { unreachable!() }
+
+    fn accept_entity_visitor(visitor: &mut impl EntityVisitor);
+}
+
+pub trait DatabaseItemVisitor {
+    fn visit<DI: DatabaseItem>(&mut self) where Self: Sized;
+}
+
+pub trait DatabaseSpec {
+    fn give_connection(&mut self, conn: std::sync::Arc<sqlite::ConnectionThreadSafe>);
+    fn accept_entity_visitor(visitor: &mut impl EntityVisitor);
+}
+
+impl<T: Entity> DatabaseSpec for IDMap<T> {
+    fn give_connection(&mut self, conn: std::sync::Arc<sqlite::ConnectionThreadSafe>) {
+        self.conn.set(conn).ok().expect("couldn't set once_cell with sqlite connection!");
+    }
+    fn accept_entity_visitor(visitor: &mut impl EntityVisitor) {
+        visitor.visit::<T>()
+    }
+}
+
+pub trait Database {
+    fn accept_item_visitor(visitor: &mut impl DatabaseItemVisitor) where Self: Sized;
+}
+
+
+#[cfg(test)]
+mod simple_test {
+    use super::*;
+    use crate::entity::{EntityPart,EntityPartVisitor};
+    // simple hand-built database example
+
+    struct SimpleEntity {
+        name: Unique<String>,
+    }
+
+    // normally this would be invisible, but for testing purposes we expose it
+    impl SimpleEntity {
+        // pub const Name: SimpleEntityName = SimpleEntityName;
+    }
+    struct SimpleEntityName;
+    impl EntityPart for SimpleEntityName {
+        type Datum = String;
+        fn part_name() -> &'static str { "name" }
+        fn is_unique() -> bool { true }
+    }
+
+    impl Entity for SimpleEntity {
+        fn entity_name() -> &'static str { "simple_entity" }
+        fn accept_part_visitor(visitor: &mut impl EntityPartVisitor) {
+            visitor.visit::<SimpleEntityName>();
+        }
+    }
+
+    struct SimpleDatabase {
+        strings: IDMap<SimpleEntity>,
+    }
+
+    impl Database for SimpleDatabase {
+        fn accept_item_visitor(visitor: &mut impl DatabaseItemVisitor) where Self: Sized {
+            struct SimpleDatabaseStringsItem;
+            impl DatabaseItem for SimpleDatabaseStringsItem {
+                fn item_key() -> &'static str { "strings" }
+                fn dependency_keys() -> &'static [&'static str] { &[] }
+
+                fn accept_entity_visitor(visitor: &mut impl EntityVisitor) {
+                    visitor.visit::<SimpleEntity>();
+                }
+            }
+
+            visitor.visit::<SimpleDatabaseStringsItem>();
+        }
+    }
+
+    #[test]
+    fn part_visitor() {
+        struct V { 
+            v: Vec<std::any::TypeId>,
+        }
+        impl EntityPartVisitor for V {
+            fn visit<EP: EntityPart>(&mut self) {
+                self.v.push(std::any::TypeId::of::<EP>());
+            }
+        }
+
+        let mut vis = V { v: vec![] };
+        SimpleEntity::accept_part_visitor(&mut vis);
+        assert_eq!(vis.v.as_slice(), &[std::any::TypeId::of::<SimpleEntityName>()]);
+    }
+}
+
+#[cfg(test)]
+mod derive_test {
+    use microrm_macros::{Database, Entity};
+
+    use super::IDMap;
+    #[derive(Entity)]
+    struct SimpleEntity {
+        name: super::Unique<String>
+    }
+
+    #[derive(Database)]
+    struct SimpleDB {
+        entities: IDMap<SimpleEntity>
+    }
+
+    #[test]
+    fn collect_test() {
+        crate::schema::collect_from_database::<SimpleDB>();
+    }
+}
+
+
+
+/*
+// pub trait 
+
+pub struct Skill { }
+impl Entity for Skill {}
+
+pub struct MenuItem {
+    name: String,
+    required_skills: Set<Skill>,
+    cost: f32,
+}
+
+impl Entity for MenuItem {}
+
+struct EmployeeShiftMap;
+
+pub struct Employee {
+    assigned_shifts: NamedMap<EmployeeShiftMap, Shift>,
+}
+
+impl Entity for Employee {}
+
+pub struct Shift {
+    name: String,
+    employees: Set<Employee>,
+}
+
+impl Entity for Shift {}
+
+pub struct FoodTruckSchema {
+    menu: Collection<MenuItem>,
+}
+*/

+ 23 - 55
microrm/src/entity.rs

@@ -1,65 +1,33 @@
-use crate::model::Modelable;
-
-/// A database entity, aka a struct representing a row in a table
-pub trait Entity: 'static + for<'de> serde::Deserialize<'de> + serde::Serialize {
-    type ID: EntityID;
-    type IDColumn: EntityColumn<Entity = Self>;
-    fn table_name() -> &'static str;
-    fn column_count() -> usize
-    where
-        Self: Sized;
-    fn columns() -> &'static [&'static dyn EntityColumn<Entity = Self>];
-    fn id_column() -> Self::IDColumn;
-
-    fn visit_values<E, F: FnMut(&dyn Modelable) -> Result<(), E>>(
-        &self,
-        visit: &mut F,
-    ) -> Result<(), E>;
-
-    fn build_from(stmt: &sqlite::Statement) -> sqlite::Result<Self>
-    where
-        Self: Sized;
+/// Represents a data field in an Entity
+pub trait EntityDatum: 'static {
+    
 }
 
-/// Trait representing the columns of a database entity
-pub trait EntityColumn: 'static + Send + Sync {
-    type Entity: Entity;
+impl EntityDatum for String {}
 
-    fn table_name(&self) -> &'static str {
-        <Self::Entity as Entity>::table_name()
-    }
+/// A single data field in an Entity
+pub trait EntityPart: 'static {
+    type Datum: EntityDatum;
 
-    fn column_typeid(&self) -> std::any::TypeId {
-        std::any::TypeId::of::<Self>()
-    }
-
-    fn sql_type(&self) -> &'static str;
-    fn index(&self) -> usize;
-    fn name(&self) -> &'static str;
+    fn part_name() -> &'static str;
+    fn is_unique() -> bool { false }
+    fn is_assoc() -> bool { false }
+    fn assoc_name() -> &'static str { unreachable!() }
+    fn visit_assoc(_: &mut impl EntityVisitor) { unreachable!() }
+}
 
-    fn fk_table_name(&self) -> Option<&'static str>;
-    fn fk_column_name(&self) -> Option<&'static str>;
+/// Visitor for traversing all `EntityPart`s in an `Entity`
+pub trait EntityPartVisitor {
+    fn visit<EP: EntityPart>(&mut self);
 }
 
-/// Trait for entity IDs in the database
-pub trait EntityID: 'static + std::fmt::Debug + Copy + Modelable {
-    type Entity: Entity;
-    fn from_raw_id(raw: i64) -> Self;
-    fn raw_id(&self) -> i64;
+/// A single database entity, aka an object type that gets its own table
+pub trait Entity: 'static {
+    fn entity_name() -> &'static str;
+    fn accept_part_visitor(visitor: &mut impl EntityPartVisitor);
 }
 
-/// Trait for an index over a column
-pub trait Index {
-    fn index_name() -> &'static str
-    where
-        Self: Sized;
-    fn table_name() -> &'static str
-    where
-        Self: Sized;
-    fn column_names() -> &'static [&'static str]
-    where
-        Self: Sized;
-    fn unique() -> bool
-    where
-        Self: Sized;
+/// Visitor for traversing all `Entity`s in a container
+pub trait EntityVisitor {
+    fn visit<E: Entity>(&mut self);
 }

+ 0 - 44
microrm/src/error.rs

@@ -1,44 +0,0 @@
-#[derive(Debug)]
-pub enum Error {
-    DBError(sqlite::Error),
-    LoadError(String),
-    StoreError(String),
-    EmptyStoreError,
-    CreateError,
-    ExecFailure,
-}
-
-impl From<sqlite::Error> for Error {
-    fn from(e: sqlite::Error) -> Self {
-        Self::DBError(e)
-    }
-}
-
-impl From<Error> for sqlite::Error {
-    fn from(e: Error) -> Self {
-        match e {
-            Error::DBError(e) => e,
-            _ => panic!(),
-        }
-    }
-}
-
-impl std::fmt::Display for Error {
-    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
-        fmt.write_fmt(format_args!("{:?}", self))
-    }
-}
-
-impl serde::ser::Error for Error {
-    fn custom<T: std::fmt::Display>(msg: T) -> Self {
-        Self::StoreError(format!("{}", msg))
-    }
-}
-
-impl serde::de::Error for Error {
-    fn custom<T: std::fmt::Display>(msg: T) -> Self {
-        Self::LoadError(format!("{}", msg))
-    }
-}
-
-impl std::error::Error for Error {}

+ 1 - 565
microrm/src/lib.rs

@@ -1,567 +1,3 @@
-#![doc = include_str!("../README.md")]
-
 pub mod entity;
-mod error;
-mod meta;
-pub mod model;
-pub mod query;
 pub mod schema;
-
-use entity::Entity;
-use meta::Metaschema;
-
-pub use microrm_macros::{make_index, Entity, Modelable};
-
-pub use error::Error;
-pub use query::{build::CompareOp, QueryInterface, WithID};
-pub use schema::Schema;
-
-pub mod prelude {
-    pub use crate::query::{Filterable, Resolvable, Settable};
-}
-
-use prelude::*;
-
-#[macro_export]
-macro_rules! value_list {
-    ( $( $element:expr ),* ) => {
-        [ $( &($element) as &dyn $crate::model::Modelable ),* ]
-    }
-}
-
-// no need to show the re-exports in the documentation
-#[doc(hidden)]
-pub mod re_export {
-    pub use lazy_static;
-    pub use serde;
-    pub use serde_json;
-    pub use sqlite;
-}
-
-#[derive(Debug)]
-pub enum DBError {
-    ConnectFailure,
-    EarlyFailure(sqlite::Error),
-    NoSchema,
-    DifferentSchema,
-    DropFailure,
-    CreateFailure,
-    SanityCheckFailure,
-    InternalFailure(crate::Error),
-}
-
-impl From<crate::Error> for DBError {
-    fn from(err: crate::Error) -> Self {
-        Self::InternalFailure(err)
-    }
-}
-
-#[derive(PartialEq, Debug)]
-pub enum CreateMode {
-    /// The database must exist and have a valid schema already
-    MustExist,
-    /// It's fine if the database doesn't exist, but it must have a valid schema if it does
-    AllowNewDatabase,
-    /// Nuke the contents if need be, just get the database
-    AllowSchemaUpdate,
-}
-
-impl std::fmt::Display for DBError {
-    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
-        fmt.write_fmt(format_args!("Database error: {:?}", self))
-    }
-}
-
-impl std::error::Error for DBError {}
-
-/// SQLite database connection
-pub struct DB {
-    conn: sqlite::Connection,
-    schema_hash: String,
-    schema: schema::Schema,
-}
-
-impl DB {
-    pub fn new(schema: schema::Schema, path: &str, mode: CreateMode) -> Result<Self, DBError> {
-        Self::from_connection(
-            sqlite::Connection::open(path).map_err(|_| DBError::ConnectFailure)?,
-            schema,
-            mode,
-        )
-    }
-
-    /// Mostly for use in tests, but may be useful in some applications as well.
-    pub fn new_in_memory(schema: schema::Schema) -> Result<Self, DBError> {
-        Self::from_connection(
-            sqlite::Connection::open(":memory:").map_err(|_| DBError::ConnectFailure)?,
-            schema,
-            CreateMode::AllowNewDatabase,
-        )
-    }
-
-    /// Get a query interface for this DB connection
-    pub fn query_interface(&self) -> query::QueryInterface {
-        query::QueryInterface::new(self)
-    }
-
-    pub fn recreate_schema(&self) -> Result<(), DBError> {
-        self.create_schema()
-    }
-
-    fn from_connection(
-        conn: sqlite::Connection,
-        schema: schema::Schema,
-        mode: CreateMode,
-    ) -> Result<Self, DBError> {
-        let sig = Self::calculate_schema_hash(&schema);
-        let ret = Self {
-            conn,
-            schema_hash: sig,
-            schema: schema.add::<meta::Metaschema>(),
-        };
-        ret.check_schema(mode)?;
-        Ok(ret)
-    }
-
-    fn calculate_schema_hash(schema: &schema::Schema) -> String {
-        use sha2::Digest;
-
-        let mut hasher = sha2::Sha256::new();
-        schema
-            .drop()
-            .iter()
-            .map(|sql| hasher.update(sql.as_bytes()))
-            .count();
-        schema
-            .create()
-            .iter()
-            .map(|sql| hasher.update(sql.as_bytes()))
-            .count();
-
-        base64::encode(hasher.finalize())
-    }
-
-    fn check_schema(&self, mode: CreateMode) -> Result<(), DBError> {
-        let mut has_metaschema = false;
-        self.conn
-            .iterate(
-                format!(
-                    "SELECT * FROM \"sqlite_master\" WHERE \"type\"='table' AND \"name\"='{}'",
-                    Metaschema::table_name()
-                ),
-                |_row| {
-                    has_metaschema = true;
-                    true
-                },
-            )
-            .map_err(DBError::EarlyFailure)?;
-
-        if !has_metaschema && mode != CreateMode::MustExist {
-            return self.create_schema();
-        } else if !has_metaschema && mode == CreateMode::MustExist {
-            return Err(DBError::NoSchema);
-        }
-
-        let qi = query::QueryInterface::new(self);
-        let hash: Option<WithID<Metaschema>> =
-            qi.get().by(meta::Metaschema::Key, "schema_hash").one()?;
-
-        if hash.is_none() {
-            if mode == CreateMode::MustExist {
-                return Err(DBError::NoSchema);
-            }
-            return self.create_schema();
-        } else if hash.unwrap().value != self.schema_hash {
-            if mode != CreateMode::AllowSchemaUpdate {
-                return Err(DBError::DifferentSchema);
-            }
-            self.drop_schema()?;
-            return self.create_schema();
-        }
-
-        Ok(())
-    }
-
-    fn drop_schema(&self) -> Result<(), DBError> {
-        for ds in self.schema.drop() {
-            self.conn.execute(ds).map_err(|_| DBError::DropFailure)?;
-        }
-        Ok(())
-    }
-
-    fn create_schema(&self) -> Result<(), DBError> {
-        for cs in self.schema.create() {
-            self.conn.execute(cs).map_err(|_| DBError::CreateFailure)?;
-        }
-
-        let qi = query::QueryInterface::new(self);
-
-        let add_result = qi.add(&meta::Metaschema {
-            key: "schema_hash".to_string(),
-            value: self.schema_hash.clone(),
-        });
-
-        assert!(add_result.is_ok());
-
-        let sanity_check = qi.get().by(meta::Metaschema::Key, "schema_hash").one();
-        assert!(sanity_check.is_ok() && sanity_check.as_ref().unwrap().is_some());
-        assert_eq!(sanity_check.unwrap().unwrap().value, self.schema_hash);
-
-        Ok(())
-    }
-}
-
-/// Add support for multi-threading to a `DB`.
-///
-/// This is a thread-local cache that carefully maintains the property that no
-/// element of the cache will ever be accessed in any way from another thread. The only
-/// way to maintain this property is to leak all data, so this is best used
-/// in lightly-threaded programs (or at least a context where threads are long-lived).
-/// All cached values are assumed to use interior mutability where needed to maintain state.
-///
-/// This approach ensures that all items can live for the provided lifetime `'l`.
-pub struct DBPool<'a> {
-    // normally DB is not Send because the raw sqlite ptr is not Send
-    // however we assume sqlite is operating in serialized mode, which means
-    // that it is in fact both `Send` and `Sync`
-    db: &'a DB,
-    // we carefully maintain the invariant here that only the thread with the given `ThreadId`
-    // accesses the QueryInterface part of the pair, which means that despite the fact that
-    // QueryInterface is neither Send nor Sync can be dismissed in this Send and Sync container
-    qi: std::sync::RwLock<Vec<(std::thread::ThreadId, &'a QueryInterface<'a>)>>,
-}
-
-impl<'a> DBPool<'a> {
-    pub fn new(db: &'a DB) -> Self {
-        Self {
-            db,
-            qi: std::sync::RwLock::new(Vec::new()),
-        }
-    }
-
-    /// Get a query interface from this DB pool for the current thread
-    pub fn query_interface(&self) -> &query::QueryInterface<'a> {
-        let guard = self.qi.read().expect("Couldn't acquire read lock");
-        let current_id = std::thread::current().id();
-        if let Some(res) = guard
-            .iter()
-            .find_map(|x| if x.0 == current_id { Some(x.1) } else { None })
-        {
-            return res;
-        }
-
-        drop(guard);
-        let mut guard = self.qi.write().expect("Couldn't acquire write lock");
-        guard.push((current_id, Box::leak(Box::new(self.db.query_interface()))));
-        drop(guard);
-
-        self.query_interface()
-    }
-}
-
-/// We carefully implement `DBPool` so that it is `Send`.
-unsafe impl<'a> Send for DBPool<'a> {}
-/// We carefully implement `DBPool` so that it is `Sync`.
-unsafe impl<'a> Sync for DBPool<'a> {}
-
-#[cfg(test)]
-mod pool_test {
-    trait IsSend: Send {}
-    impl IsSend for super::DB {}
-    impl<'a> IsSend for super::DBPool<'a> {}
-    // we make sure that DBPool is send / sync safe
-    trait IsSendAndSync: Send + Sync {}
-    impl<'a> IsSendAndSync for super::DBPool<'a> {}
-}
-
-#[cfg(test)]
-mod test_support {
-    // use crate::prelude::*;
-
-    #[derive(Debug, crate::Entity, serde::Serialize, serde::Deserialize)]
-    #[microrm_internal]
-    pub struct KVStore {
-        pub key: String,
-        pub value: String,
-    }
-
-    // pub const SCHEMA : crate::Schema = crate::Schema::new().entity::<KVStore>();
-
-    pub fn random_filename() -> std::path::PathBuf {
-        use rand::prelude::Distribution;
-        let dist = rand::distributions::Uniform::new('a', 'z');
-        let mut db_filename = std::env::temp_dir();
-        let mut rng = rand::thread_rng();
-        db_filename.push(format!("microrm-{}.db", (0..16).map(|_| dist.sample(&mut rng)).collect::<String>()));
-        db_filename
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use crate::prelude::*;
-
-    use super::DB;
-
-    #[derive(serde::Serialize, serde::Deserialize, crate::Entity)]
-    #[microrm_internal]
-    pub struct S1 {
-        an_id: i32,
-    }
-
-    fn simple_schema() -> crate::Schema {
-        crate::Schema::new().add::<S1>()
-    }
-
-    #[test]
-    fn in_memory_schema() {
-        let _db = DB::new_in_memory(simple_schema());
-        drop(_db);
-    }
-
-    #[derive(serde::Serialize, serde::Deserialize, crate::Entity)]
-    #[microrm_internal]
-    pub struct S2 {
-        #[microrm_foreign]
-        parent_id: S1ID,
-    }
-
-    #[test]
-    fn simple_foreign_key() {
-        let db = DB::new_in_memory(crate::Schema::new().add::<S1>().add::<S2>())
-            .expect("Can't connect to in-memory DB");
-        let qi = db.query_interface();
-
-        let id = qi.add(&S1 { an_id: -1 }).expect("Can't add S1");
-        let child_id = qi.add(&S2 { parent_id: id }).expect("Can't add S2");
-
-        qi.get()
-            .by(S2::ID, &child_id)
-            .one()
-            .expect("Can't get S2 instance");
-    }
-
-    microrm_macros::make_index_internal!(S2ParentIndex, S2::ParentId);
-}
-
-#[cfg(test)]
-mod test2 {
-    use crate::prelude::*;
-
-    #[derive(Debug, crate::Entity, serde::Serialize, serde::Deserialize)]
-    #[microrm_internal]
-    pub struct KVStore {
-        pub key: String,
-        pub value: String,
-    }
-
-    // the !KVStoreIndex here means a type representing a unique index named KVStoreIndex
-    microrm_macros::make_index_internal!(!KVStoreIndex, KVStore::Key);
-
-    #[test]
-    fn dump_test() {
-        let schema = crate::Schema::new()
-            .add::<KVStore>()
-            .index::<KVStoreIndex>();
-
-        // dump the schema in case you want to inspect it manually
-        for create_sql in schema.create() {
-            println!("{};", create_sql);
-        }
-
-        let db = crate::DB::new_in_memory(schema).unwrap();
-        let qi = db.query_interface();
-
-        qi.add(&KVStore {
-            key: "a_key".to_string(),
-            value: "a_value".to_string(),
-        })
-        .unwrap();
-
-        // because KVStoreIndex indexes key, this is a logarithmic lookup
-        let qr = qi.get().by(KVStore::Key, "a_key").one();
-
-        assert_eq!(qr.is_ok(), true);
-        assert_eq!(qr.as_ref().unwrap().is_some(), true);
-        assert_eq!(qr.as_ref().unwrap().as_ref().unwrap().key, "a_key");
-        assert_eq!(qr.as_ref().unwrap().as_ref().unwrap().value, "a_value");
-    }
-}
-
-#[cfg(test)]
-mod delete_test {
-    use crate::prelude::*;
-
-    #[derive(Debug, crate::Entity, serde::Serialize, serde::Deserialize)]
-    #[microrm_internal]
-    pub struct KVStore {
-        pub key: String,
-        pub value: String,
-    }
-
-    #[test]
-    fn delete_test() {
-        let schema = crate::Schema::new().entity::<KVStore>();
-
-        let db = crate::DB::new_in_memory(schema).unwrap();
-        let qi = db.query_interface();
-
-        qi.add(&KVStore {
-            key: "a".to_string(),
-            value: "a_value".to_string(),
-        })
-        .unwrap();
-
-        let insert_two = || {
-            qi.add(&KVStore {
-                key: "a".to_string(),
-                value: "a_value".to_string(),
-            })
-            .unwrap();
-
-            qi.add(&KVStore {
-                key: "a".to_string(),
-                value: "another_value".to_string(),
-            })
-            .unwrap();
-        };
-
-        assert!(qi.get().by(KVStore::Key, "a").one().is_ok());
-        assert!(qi.delete().by(KVStore::Key, "a").exec().is_ok());
-        assert!(qi.get().by(KVStore::Key, "a").one().unwrap().is_none());
-
-        insert_two();
-
-        let all = qi.get().by(KVStore::Key, "a").all();
-        assert!(all.is_ok());
-        assert_eq!(all.unwrap().len(), 2);
-
-        assert!(qi.delete().by(KVStore::Key, "b").exec().is_ok());
-
-        let all = qi.get().by(KVStore::Key, "a").all();
-        assert!(all.is_ok());
-        assert_eq!(all.unwrap().len(), 2);
-
-        assert!(qi
-            .delete()
-            .by(KVStore::Key, &"a")
-            .by(KVStore::Value, &"another_value")
-            .exec()
-            .is_ok());
-
-        let one = qi.get().by(KVStore::Key, "a").one().unwrap();
-        assert!(one.is_some());
-        assert_eq!(one.unwrap().value, "a_value");
-    }
-}
-
-#[cfg(test)]
-mod datatypes {
-    use crate::prelude::*;
-
-    #[derive(crate::Entity,serde::Serialize,serde::Deserialize,PartialEq,Debug)]
-    #[microrm_internal]
-    pub struct ValueStore {
-        pub b: bool,
-        pub i_8: i8,
-        pub u_8: u8,
-        pub i_16: i16,
-        pub u_16: u16,
-        pub i_32: i32,
-        pub u_32: u32,
-        pub i_64: i64,
-        pub u_64: u64,
-        pub s: String,
-        pub f_64: f64,
-    }
-
-
-    #[test]
-    fn store_load_datatypes() {
-        let schema = crate::Schema::new().entity::<ValueStore>();
-        let db = crate::DB::new_in_memory(schema).unwrap();
-
-        let test_values = ValueStore {
-            b: false,
-            i_8: 42i8,
-            u_8: 142u8,
-            i_16: 320i16,
-            u_16: 20000u16,
-            i_32: 1i32 << 20,
-            u_32: 3u32 << 30,
-            i_64: 1i64 << 40,
-            u_64: 3u64 << 62,
-            s: "this is a test".to_string(),
-            // e**pi
-            f_64: 23.140692632779263f64
-        };
-
-        let id = db.query_interface().add(&test_values).expect("failed to add ValueStore");
-
-        let all = db.query_interface().get().by_id(&id).all().expect("failed to get by id");
-        assert_eq!(all.len(), 1);
-        assert_eq!(all[0].as_ref(), &test_values);
-    }
-}
-
-#[cfg(test)]
-mod disk_tests {
-
-    use crate::prelude::*;
-
-    #[test]
-    fn store_and_load_kv() {
-        let path = crate::test_support::random_filename();
-        let path_str = path.clone().into_os_string().into_string().unwrap();
-
-        {
-            let schema = crate::Schema::new().entity::<crate::test_support::KVStore>();
-            let db = crate::DB::new(schema, &path_str, crate::CreateMode::AllowNewDatabase).unwrap();
-
-            db.query_interface().add(&crate::test_support::KVStore {
-                key: "key".into(),
-                value: "val".into()
-            }).expect("couldn't add");
-        }
-
-        {
-            let schema = crate::Schema::new().entity::<crate::test_support::KVStore>();
-            let db = crate::DB::new(schema, &path_str, crate::CreateMode::MustExist).unwrap();
-
-            let all = db.query_interface().get::<crate::test_support::KVStore>().all().expect("couldn't get all kv");
-
-            assert_eq!(all.len(), 1);
-        }
-
-        std::fs::remove_file(path).expect("Couldn't remove temporary file!");
-    }
-
-    #[test]
-    fn store_and_load_kv_dbp() {
-        let path = crate::test_support::random_filename();
-        let path_str = path.clone().into_os_string().into_string().unwrap();
-
-        {
-            let schema = crate::Schema::new().entity::<crate::test_support::KVStore>();
-            let db = crate::DB::new(schema, &path_str, crate::CreateMode::AllowNewDatabase).unwrap();
-            let dbp = crate::DBPool::new(&db);
-
-            dbp.query_interface().add(&crate::test_support::KVStore {
-                key: "key".into(),
-                value: "val".into()
-            }).expect("couldn't add");
-        }
-
-        {
-            let schema = crate::Schema::new().entity::<crate::test_support::KVStore>();
-            let db = crate::DB::new(schema, &path_str, crate::CreateMode::MustExist).unwrap();
-            let dbp = crate::DBPool::new(&db);
-
-            let all = dbp.query_interface().get::<crate::test_support::KVStore>().all().expect("couldn't get all kv");
-
-            assert_eq!(all.len(), 1);
-        }
-
-        std::fs::remove_file(path).expect("Couldn't remove temporary file!");
-    }
-}
+pub mod db;

+ 0 - 8
microrm/src/meta.rs

@@ -1,8 +0,0 @@
-use crate::Entity;
-
-#[derive(Debug, Entity, serde::Serialize, serde::Deserialize)]
-#[microrm_internal]
-pub struct Metaschema {
-    pub key: String,
-    pub value: String,
-}

+ 0 - 24
microrm/src/model.rs

@@ -1,24 +0,0 @@
-pub(crate) mod store;
-
-// Modelable implementations
-mod modelable;
-mod json;
-
-pub use json::JsonWrapper;
-
-/// A database value, aka a single column of a single row
-pub trait Modelable {
-    fn bind_to(&self, stmt: &mut sqlite::Statement, col: usize) -> sqlite::Result<()>;
-    fn build_from(_stmt: &sqlite::Statement, _col_offset: usize) -> sqlite::Result<(Self, usize)>
-    where
-        Self: Sized,
-    {
-        unreachable!()
-    }
-    fn column_type() -> &'static str
-    where
-        Self: Sized,
-    {
-        unreachable!()
-    }
-}

+ 0 - 69
microrm/src/model/json.rs

@@ -1,69 +0,0 @@
-/// Wrapper struct to store a serializable object as JSON transparently in a text column
-pub struct JsonWrapper<T: serde::Serialize + serde::de::DeserializeOwned + 'static> {
-    wrap: T
-}
-
-impl<T: serde::Serialize + serde::de::DeserializeOwned + 'static> serde::Serialize for JsonWrapper<T> {
-    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
-        self.wrap.serialize(serializer)
-    }
-}
-
-impl<'de, T: serde::Serialize + serde::de::DeserializeOwned + 'static> serde::Deserialize<'de> for JsonWrapper<T> {
-    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
-        Ok(Self::wrap(T::deserialize(deserializer)?))
-    }
-}
-
-impl<T: serde::Serialize + serde::de::DeserializeOwned + 'static> JsonWrapper<T> {
-    pub fn wrap(wrap: T) -> Self {
-        Self { wrap }
-    }
-
-    pub fn unwrap(self) -> T {
-        self.wrap
-    }
-}
-
-impl<T: serde::Serialize + serde::de::DeserializeOwned + 'static> AsRef<T> for JsonWrapper<T> {
-    fn as_ref(&self) -> &T {
-        &self.wrap
-    }
-}
-
-impl<T: serde::Serialize + serde::de::DeserializeOwned + 'static> AsMut<T> for JsonWrapper<T> {
-    fn as_mut(&mut self) -> &mut T {
-        &mut self.wrap
-    }
-}
-
-impl<T: serde::Serialize + serde::de::DeserializeOwned + 'static> From<T> for JsonWrapper<T> {
-    fn from(wrap: T) -> Self {
-        Self { wrap }
-    }
-}
-
-impl<T: serde::Serialize + serde::de::DeserializeOwned + 'static> super::Modelable for JsonWrapper<T> {
-    fn bind_to(&self, stmt: &mut sqlite::Statement, col: usize) -> sqlite::Result<()> {
-        serde_json::to_string(&self.wrap).unwrap().bind_to(stmt, col)
-    }
-    fn build_from(stmt: &sqlite::Statement, col_offset: usize) -> sqlite::Result<(Self, usize)>
-    where
-        Self: Sized,
-    {
-        let s = String::build_from(stmt, col_offset)?;
-        Ok((
-            Self::wrap(serde_json::from_str::<T>(s.0.as_str()).map_err(|e| sqlite::Error {
-                code: None,
-                message: Some(e.to_string()),
-            })?),
-            1,
-        ))
-    }
-    fn column_type() -> &'static str
-    where
-        Self: Sized,
-    {
-        "text"
-    }
-}

+ 0 - 233
microrm/src/model/modelable.rs

@@ -1,233 +0,0 @@
-use super::Modelable;
-use sqlite::Bindable;
-
-macro_rules! integral {
-    ($i:ty) => {
-        impl Modelable for $i {
-            fn bind_to(&self, stmt: &mut sqlite::Statement, col: usize) -> sqlite::Result<()> {
-                (*self as i64).bind(stmt, col)
-            }
-            fn build_from(
-                stmt: &sqlite::Statement,
-                col_offset: usize,
-            ) -> sqlite::Result<(Self, usize)>
-            where
-                Self: Sized,
-            {
-                stmt.read::<i64>(col_offset).map(|x| (x as Self, 1))
-            }
-
-            fn column_type() -> &'static str
-            where
-                Self: Sized,
-            {
-                "integer"
-            }
-        }
-    };
-}
-
-integral!(i8);
-integral!(u8);
-integral!(i16);
-integral!(u16);
-integral!(i32);
-integral!(u32);
-integral!(i64);
-integral!(u64);
-integral!(usize);
-
-impl Modelable for f64 {
-    fn bind_to(&self, stmt: &mut sqlite::Statement, col: usize) -> sqlite::Result<()> {
-        self.bind(stmt, col)
-    }
-    fn build_from(stmt: &sqlite::Statement, col_offset: usize) -> sqlite::Result<(Self, usize)>
-    where
-        Self: Sized,
-    {
-        stmt.read(col_offset).map(|x| (x, 1))
-    }
-    fn column_type() -> &'static str
-    where
-        Self: Sized,
-    {
-        "numeric"
-    }
-}
-
-impl Modelable for bool {
-    fn bind_to(&self, stmt: &mut sqlite::Statement, col: usize) -> sqlite::Result<()> {
-        let val = if self == &true { 1i64 } else { 0i64 };
-        val.bind(stmt, col)
-    }
-    fn build_from(stmt: &sqlite::Statement, col_offset: usize) -> sqlite::Result<(Self, usize)>
-    where
-        Self: Sized,
-    {
-        stmt.read(col_offset).map(|x: i64| (x != 0,1))
-    }
-    fn column_type() -> &'static str
-    where
-        Self: Sized,
-    {
-        "integer"
-    }
-}
-
-impl<'a> Modelable for &'a str {
-    fn bind_to(&self, stmt: &mut sqlite::Statement, col: usize) -> sqlite::Result<()> {
-        self.bind(stmt, col)
-    }
-    fn build_from(_stmt: &sqlite::Statement, _col_offset: usize) -> sqlite::Result<(Self, usize)>
-    where
-        Self: Sized,
-    {
-        unreachable!("sqlite only gives Strings back, not &strs!");
-    }
-
-    fn column_type() -> &'static str
-    where
-        Self: Sized,
-    {
-        "text"
-    }
-}
-
-impl Modelable for str {
-    fn bind_to(&self, stmt: &mut sqlite::Statement, col: usize) -> sqlite::Result<()> {
-        self.bind(stmt, col)
-    }
-}
-
-impl Modelable for std::string::String {
-    fn bind_to(&self, stmt: &mut sqlite::Statement, col: usize) -> sqlite::Result<()> {
-        self.as_str().bind(stmt, col)
-    }
-    fn build_from(stmt: &sqlite::Statement, col_offset: usize) -> sqlite::Result<(Self, usize)>
-    where
-        Self: Sized,
-    {
-        stmt.read(col_offset).map(|x| (x, 1))
-    }
-    fn column_type() -> &'static str
-    where
-        Self: Sized,
-    {
-        "text"
-    }
-}
-
-impl<'a> Modelable for &'a [u8] {
-    fn bind_to(&self, stmt: &mut sqlite::Statement, col: usize) -> sqlite::Result<()> {
-        self.bind(stmt, col)
-    }
-    fn build_from(_stmt: &sqlite::Statement, _col_offset: usize) -> sqlite::Result<(Self, usize)>
-    where
-        Self: Sized,
-    {
-        unreachable!("sqlite only gives Vec<u8> back, not &[u8]!");
-    }
-    fn column_type() -> &'static str
-    where
-        Self: Sized,
-    {
-        "blob"
-    }
-}
-
-impl<'a, T: Modelable> Modelable for &'a T {
-    fn bind_to(&self, stmt: &mut sqlite::Statement, col: usize) -> sqlite::Result<()> {
-        <T as Modelable>::bind_to(self, stmt, col)
-    }
-    fn build_from(_stmt: &sqlite::Statement, _col_offset: usize) -> sqlite::Result<(Self, usize)>
-    where
-        Self: Sized,
-    {
-        unreachable!();
-    }
-    fn column_type() -> &'static str
-    where
-        Self: Sized,
-    {
-        unreachable!();
-    }
-}
-
-impl<T: Modelable> Modelable for Option<T> {
-    fn bind_to(&self, stmt: &mut sqlite::Statement, col: usize) -> sqlite::Result<()> {
-
-        match self.as_ref() {
-            Some(val) => {
-                val.bind_to(stmt, col)
-            }
-            None => {
-                stmt.bind(col, &sqlite::Value::Null)
-            }
-        }
-    }
-    fn build_from(stmt: &sqlite::Statement, col_offset: usize) -> sqlite::Result<(Self, usize)>
-    where
-        Self: Sized,
-    {
-        // note: this is needlessly expensive since we read things twice.
-        let val = stmt.read::<sqlite::Value>(col_offset)?;
-        if val.kind() == sqlite::Type::Null {
-            Ok((None, 1))
-        }
-        else {
-            let (val, size) = T::build_from(stmt, col_offset)?;
-            Ok((Some(val), size))
-        }
-    }
-    fn column_type() -> &'static str
-    where
-        Self: Sized,
-    {
-        T::column_type()
-    }
-}
-
-impl<T: Modelable + serde::Serialize + serde::de::DeserializeOwned + 'static> Modelable for Vec<T> {
-    fn bind_to(&self, stmt: &mut sqlite::Statement, col: usize) -> sqlite::Result<()> {
-        // We serialize Vec<u8> types directly as a blob
-        if std::mem::size_of::<T>() == 1
-            && std::any::TypeId::of::<T>() == std::any::TypeId::of::<u8>()
-        {
-            // this bit is unsafe, but is perfectly reasonable...
-            let byte_slice =
-                unsafe { std::slice::from_raw_parts(self.as_ptr() as *const u8, self.len()) };
-            return byte_slice.bind_to(stmt, col);
-        }
-        serde_json::to_string(self).unwrap().bind_to(stmt, col)
-    }
-    fn build_from(stmt: &sqlite::Statement, col_offset: usize) -> sqlite::Result<(Self, usize)>
-    where
-        Self: Sized,
-    {
-        // Deserialize one-byte types directly from the blob
-        if std::mem::size_of::<T>() == 1
-            && std::any::TypeId::of::<T>() == std::any::TypeId::of::<u8>()
-        {
-            let blob: Vec<u8> = stmt.read(col_offset)?;
-
-            // we know the return value is a u8 because the typeid matches, so while normally this
-            // is hilariously unsafe, right now it's perfectly okay.
-            Ok((unsafe { std::mem::transmute::<Vec<u8>, Vec<T>>(blob) }, 1))
-        } else {
-            let s = String::build_from(stmt, col_offset)?;
-            Ok((
-                serde_json::from_str::<Vec<T>>(s.0.as_str()).map_err(|e| sqlite::Error {
-                    code: None,
-                    message: Some(e.to_string()),
-                })?,
-                1,
-            ))
-        }
-    }
-    fn column_type() -> &'static str
-    where
-        Self: Sized,
-    {
-        "blob"
-    }
-}

+ 0 - 11
microrm/src/model/store.rs

@@ -1,11 +0,0 @@
-use crate::entity::Entity;
-
-pub fn serialize_into<T: Entity>(stmt: &mut sqlite::Statement, value: &T) -> sqlite::Result<()> {
-    let mut i = 1;
-
-    value.visit_values::<sqlite::Error, _>(&mut |val| {
-        val.bind_to(stmt, i)?;
-        i += 1;
-        Ok(())
-    })
-}

+ 0 - 245
microrm/src/query.rs

@@ -1,245 +0,0 @@
-use crate::Error;
-use std::hash::{Hash, Hasher};
-
-use crate::entity::{Entity, EntityID};
-
-pub mod build;
-pub mod delete;
-pub mod filter;
-pub mod resolve;
-pub mod select;
-pub mod update;
-
-pub use filter::Filterable;
-pub use resolve::Resolvable;
-pub use update::Settable;
-
-/// Wraps an entity with its ID, for example as a query result.
-///
-/// The wrapped value is accessible via `Deref`, so this should be mostly
-/// transparent.
-#[derive(Debug)]
-pub struct WithID<T: Entity> {
-    wrap: T,
-    id: <T as Entity>::ID,
-}
-
-impl<T: Entity> WithID<T> {
-    pub fn new(wrap: T, id: <T as Entity>::ID) -> Self {
-        Self { wrap, id }
-    }
-
-    fn wrap(what: T, raw_id: i64) -> Self {
-        Self {
-            wrap: what,
-            id: <T as Entity>::ID::from_raw_id(raw_id),
-        }
-    }
-
-    pub fn wrapped(self) -> T {
-        self.wrap
-    }
-}
-
-impl<T: Entity> WithID<T> {
-    pub fn id(&self) -> <T as Entity>::ID {
-        self.id
-    }
-}
-
-impl<T: Entity> AsRef<T> for WithID<T> {
-    fn as_ref(&self) -> &T {
-        &self.wrap
-    }
-}
-
-impl<T: Entity> std::ops::Deref for WithID<T> {
-    type Target = T;
-    fn deref(&self) -> &Self::Target {
-        &self.wrap
-    }
-}
-
-impl<T: Entity> std::ops::DerefMut for WithID<T> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.wrap
-    }
-}
-
-type CacheIndex = u64;
-
-/// The query interface for a database.
-///
-/// As the query interface provides some level of caching, try to strive for as much sharing as
-/// possible. Passing around `QueryInterface` references instead of `DB` references is a good way
-/// to achieve this. However, `QueryInterface` is explicitly `!Send`, so you may need to use
-/// something like a [`DBPool`](../struct.DBPool.html) to acquire instances locally if you're using
-/// threads or asynchronous programming.
-pub struct QueryInterface<'l> {
-    db: &'l crate::DB,
-
-    cache: std::cell::RefCell<std::collections::HashMap<CacheIndex, sqlite::Statement<'l>>>,
-
-    // use a phantom non-Send-able type to implement !Send for QueryInterface
-    prevent_send: std::marker::PhantomData<*mut ()>,
-}
-
-impl<'l> QueryInterface<'l> {
-    /// Creates a QueryInterface. Generally you probably want to be using
-    /// `DBPool::query_interface()`.
-    pub fn new(db: &'l crate::DB) -> Self {
-        Self {
-            db,
-            cache: std::cell::RefCell::new(std::collections::HashMap::new()),
-            prevent_send: std::marker::PhantomData,
-        }
-    }
-
-    /// Helper function to process an expected single result, discarding the rest
-    fn expect_one_result<T>(
-        &self,
-        stmt: &mut sqlite::Statement,
-        with_result: &mut dyn FnMut(&mut sqlite::Statement) -> Result<T, crate::Error>,
-    ) -> Result<Option<T>, crate::Error> {
-        let state = stmt.next()?;
-        if state != sqlite::State::Row {
-            return Ok(None);
-        }
-
-        Ok(Some(with_result(stmt)?))
-    }
-
-    /// Helper function to process an expected zero results
-    /// Note that this errors out if there is any result
-    fn expect_no_result(&self, stmt: &mut sqlite::Statement) -> Result<(), crate::Error> {
-        let state = stmt.next()?;
-        if state != sqlite::State::Done {
-            return Err(crate::Error::ExecFailure);
-        }
-
-        Ok(())
-    }
-}
-
-impl<'l> QueryInterface<'l> {
-    fn with_cache<
-        Return,
-        Create: Fn() -> sqlite::Statement<'l>,
-        With: FnMut(&mut sqlite::Statement<'l>) -> Return,
-    >(
-        &self,
-        hash: u64,
-        create: Create,
-        mut with: With,
-    ) -> Return
-where {
-        let mut cache = self.cache.borrow_mut();
-        let query = cache.entry(hash).or_insert_with(create);
-        let res = with(query);
-        query.reset().expect("Couldn't reset query");
-        res
-    }
-}
-
-impl<'l> QueryInterface<'l> {
-    /// Add an entity to its table in the database. Alias for `add`.
-    pub fn insert<T: Entity + serde::Serialize>(
-        &self,
-        m: &T,
-    ) -> Result<<T as Entity>::ID, crate::Error> {
-        self.add(m)
-    }
-
-    /// Add an entity to its table in the database.
-    pub fn add<T: Entity + serde::Serialize>(
-        &self,
-        m: &T,
-    ) -> Result<<T as Entity>::ID, crate::Error> {
-        let mut hasher = std::collections::hash_map::DefaultHasher::new();
-        "add".hash(&mut hasher);
-        std::any::TypeId::of::<T>().hash(&mut hasher);
-        self.with_cache(
-            hasher.finish(),
-            &|| {
-                let placeholders = (0..(<T as Entity>::column_count() - 1))
-                    .map(|_| "?".to_string())
-                    .collect::<Vec<_>>()
-                    .join(",");
-
-                self.db
-                    .conn
-                    .prepare(&format!(
-                        "INSERT INTO `{}` VALUES (NULL, {}) RETURNING \"id\"",
-                        <T as Entity>::table_name(),
-                        placeholders
-                    ))
-                    .expect("")
-            },
-            &mut |stmt: &mut sqlite::Statement<'_>| {
-                crate::model::store::serialize_into(stmt, m)?;
-
-                let rowid = self.expect_one_result(stmt, &mut |stmt| {
-                    stmt.read::<i64>(0).map_err(crate::Error::DBError)
-                })?;
-
-                Ok(<T as Entity>::ID::from_raw_id(rowid.unwrap()))
-            },
-        )
-    }
-}
-
-// General query interface
-impl<'l> QueryInterface<'l> {
-    /// Get an entity from its table, aka perform a `SELECT` query.
-    pub fn get<'a, 'b, T: Entity>(&'a self) -> select::Select<'b, 'l, T>
-    where
-        'a: 'b,
-    {
-        select::Select::new(self)
-    }
-
-    /// Update an entity in its table, aka perform an `UPDATE` query, by selecting columns.
-    pub fn update<'a, 'b, T: Entity>(&'a self) -> update::Update<'b, 'l, T>
-    where
-        'a: 'b,
-    {
-        update::Update::new(self)
-    }
-
-    /// Delete an entity from its table, aka perform a DELETE query.
-    pub fn delete<'a, 'b, T: Entity>(&'a self) -> delete::Delete<'b, 'l, T>
-    where
-        'a: 'b,
-    {
-        delete::Delete::new(self)
-    }
-}
-
-impl<'l> QueryInterface<'l> {
-    /// Update an entity in its table, aka perform an `UPDATE` query, for all columns.
-    pub fn put<T: Entity>(&self, what: &WithID<T>) -> Result<(), Error> {
-        self.update().to(what.as_ref()).by(T::id_column(), &what.id()).exec()
-    }
-}
-
-#[cfg(test)]
-mod test_build {
-    use microrm_macros::Entity;
-    use serde::{Deserialize, Serialize};
-
-    #[derive(Entity, Serialize, Deserialize)]
-    #[microrm_internal]
-    pub struct KVStore {
-        key: String,
-        value: String,
-    }
-
-    #[test]
-    fn simple_get() {
-        use super::*;
-        let db = crate::DB::new_in_memory(crate::Schema::new().entity::<KVStore>()).unwrap();
-        let qi = db.query_interface();
-
-        assert!(qi.get().by(KVStore::Key, "abc").result().is_ok());
-    }
-}

+ 0 - 106
microrm/src/query/build.rs

@@ -1,106 +0,0 @@
-//! Static in-place query construction.
-//!
-//! Interface:
-//! - `qi.get().by(KVStore::Key, &key).result()`
-
-use crate::Error;
-use std::{
-    collections::HashMap,
-    hash::{Hash, Hasher},
-};
-
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub enum QueryPart {
-    Root,
-    Set,
-    Where,
-}
-
-#[derive(Debug)]
-pub struct DerivedQuery(HashMap<QueryPart, Vec<String>>);
-
-impl DerivedQuery {
-    pub(crate) fn new() -> Self {
-        Self(HashMap::new())
-    }
-
-    pub(crate) fn add(mut self, to: QueryPart, what: String) -> Self {
-        self.0.entry(to).or_insert_with(Vec::new).push(what);
-        self
-    }
-
-    pub(crate) fn assemble(mut self) -> String {
-        let root = self.0.remove(&QueryPart::Root).unwrap().remove(0);
-
-        let set_ = match self.0.remove(&QueryPart::Set) {
-            None => String::new(),
-            Some(v) => {
-                format!(
-                    "SET {}",
-                    v.into_iter()
-                        .reduce(|a, b| format!("{}, {}", a, b))
-                        .unwrap()
-                )
-            }
-        };
-
-        let where_ = match self.0.remove(&QueryPart::Where) {
-            None => String::new(),
-            Some(v) => {
-                format!(
-                    "WHERE {}",
-                    v.into_iter()
-                        .reduce(|a, b| format!("{} AND {}", a, b))
-                        .unwrap()
-                )
-            }
-        };
-
-        log::trace!("built SQL query: {} {} {}", root, set_, where_);
-
-        format!("{} {} {}", root, set_, where_)
-    }
-}
-
-pub trait StaticVersion {
-    type Is: 'static;
-
-    fn type_id() -> std::any::TypeId {
-        std::any::TypeId::of::<Self::Is>()
-    }
-}
-
-/// Any query component
-pub trait QueryComponent: StaticVersion {
-    fn derive(&self) -> DerivedQuery {
-        DerivedQuery::new()
-    }
-
-    fn contribute<H: Hasher>(&self, hasher: &mut H);
-
-    /// returns the next index to use for binding
-    fn bind(&self, stmt: &mut sqlite::Statement<'_>) -> Result<usize, Error>;
-}
-
-#[derive(Debug)]
-pub enum CompareOp {
-    LessThan,
-    AtMost,
-    Equals,
-    AtLeast,
-    MoreThan,
-    NotEqual
-}
-
-impl CompareOp {
-    pub(crate) fn ch(&self) -> &'static str {
-        match self {
-            Self::LessThan => "<",
-            Self::AtMost => "<=",
-            Self::Equals => "=",
-            Self::AtLeast => ">=",
-            Self::MoreThan => ">",
-            Self::NotEqual => "!=",
-        }
-    }
-}

+ 0 - 49
microrm/src/query/delete.rs

@@ -1,49 +0,0 @@
-use super::build::*;
-use super::{Filterable, Resolvable};
-use crate::{Entity, Error, QueryInterface};
-use std::hash::{Hash, Hasher};
-use std::marker::PhantomData;
-
-pub struct Delete<'r, 'q, T: Entity> {
-    qi: &'r QueryInterface<'q>,
-    _ghost: PhantomData<T>,
-}
-
-impl<'r, 'q, T: Entity> Delete<'r, 'q, T> {
-    pub fn new(qi: &'r QueryInterface<'q>) -> Self {
-        Self {
-            qi,
-            _ghost: std::marker::PhantomData,
-        }
-    }
-}
-
-impl<'r, 'q, T: Entity> StaticVersion for Delete<'r, 'q, T> {
-    type Is = Delete<'static, 'static, T>;
-}
-
-impl<'r, 'q, T: Entity> QueryComponent for Delete<'r, 'q, T> {
-    fn derive(&self) -> DerivedQuery {
-        DerivedQuery::new().add(QueryPart::Root, format!("DELETE FROM `{}`", T::table_name()))
-    }
-
-    fn contribute<H: Hasher>(&self, hasher: &mut H) {
-        "delete".hash(hasher);
-        std::any::TypeId::of::<T>().hash(hasher);
-    }
-
-    // next binding point is the first
-    fn bind(&self, _stmt: &mut sqlite::Statement<'_>) -> Result<usize, Error> {
-        Ok(1)
-    }
-}
-
-impl<'r, 'q, T: Entity> Filterable<'r, 'q> for Delete<'r, 'q, T> {
-    type Table = T;
-}
-
-impl<'r, 'q, T: Entity> Resolvable<'r, 'q, T> for Delete<'r, 'q, T> {
-    fn qi(&self) -> &'r QueryInterface<'q> {
-        self.qi
-    }
-}

+ 0 - 133
microrm/src/query/filter.rs

@@ -1,133 +0,0 @@
-use super::build::*;
-use super::{build::CompareOp, Resolvable};
-use crate::entity::EntityID;
-use crate::{entity::EntityColumn, model::Modelable, Entity, QueryInterface};
-use std::hash::{Hash, Hasher};
-use std::marker::PhantomData;
-
-/// Any query that can have a WHERE clause attached to it
-pub trait Filterable<'r, 'q>: Resolvable<'r, 'q, Self::Table>
-where
-    'q: 'r,
-{
-    type Table: Entity;
-
-    fn by<C: EntityColumn<Entity = Self::Table>, G: Modelable + ?Sized>(
-        self,
-        col: C,
-        given: &'r G,
-    ) -> Filter<'r, 'q, Self, C, G>
-    where
-        Self: Sized,
-    {
-        Filter {
-            wrap: self,
-            col,
-            op: CompareOp::Equals,
-            given,
-            _ghost: PhantomData,
-        }
-    }
-
-    fn by_id<I: EntityID<Entity = Self::Table>>(
-        self,
-        given: &'r I,
-    ) -> Filter<'r, 'q, Self, <Self::Table as Entity>::IDColumn, I>
-    where
-        Self: Sized,
-    {
-        Filter {
-            wrap: self,
-            col: Self::Table::id_column(),
-            op: CompareOp::Equals,
-            given,
-            _ghost: PhantomData,
-        }
-    }
-
-    fn by_op<C: EntityColumn<Entity = Self::Table>, G: Modelable + ?Sized>(
-        self,
-        col: C,
-        op: CompareOp,
-        given: &'r G,
-    ) -> Filter<'r, 'q, Self, C, G>
-    where
-        Self: Sized,
-    {
-        Filter {
-            wrap: self,
-            col,
-            op,
-            given,
-            _ghost: PhantomData,
-        }
-    }
-}
-
-/// A concrete WHERE clause
-pub struct Filter<'r, 'q, F: Filterable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized>
-where
-    'q: 'r,
-{
-    wrap: F,
-    col: C,
-    op: CompareOp,
-    given: &'r G,
-    _ghost: PhantomData<&'q ()>,
-}
-
-impl<'r, 'q, F: Filterable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized> StaticVersion
-    for Filter<'r, 'q, F, C, G>
-where
-    <F as StaticVersion>::Is: Filterable<'static, 'static>,
-{
-    type Is = Filter<'static, 'static, <F as StaticVersion>::Is, C, u64>;
-}
-
-impl<'r, 'q, F: Filterable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized> QueryComponent
-    for Filter<'r, 'q, F, C, G>
-where
-    <F as StaticVersion>::Is: Filterable<'static, 'static>,
-    'q: 'r,
-{
-    fn derive(&self) -> DerivedQuery {
-        self.wrap.derive().add(
-            QueryPart::Where,
-            format!("`{}` {} ?", self.col.name(), self.op.ch()),
-        )
-    }
-
-    fn contribute<H: Hasher>(&self, hasher: &mut H) {
-        self.wrap.contribute(hasher);
-        std::any::TypeId::of::<Self::Is>().hash(hasher);
-        std::any::TypeId::of::<C>().hash(hasher);
-    }
-
-    fn bind(&self, stmt: &mut sqlite::Statement<'_>) -> Result<usize, crate::Error> {
-        let next_index = self.wrap.bind(stmt)?;
-
-        self.given.bind_to(stmt, next_index)?;
-
-        Ok(next_index + 1)
-    }
-}
-
-impl<'r, 'q, F: Filterable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized>
-    Resolvable<'r, 'q, C::Entity> for Filter<'r, 'q, F, C, G>
-where
-    <F as StaticVersion>::Is: Filterable<'static, 'static>,
-    'q: 'r,
-{
-    fn qi(&self) -> &'r QueryInterface<'q> {
-        self.wrap.qi()
-    }
-}
-
-impl<'r, 'q, F: Filterable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized> Filterable<'r, 'q>
-    for Filter<'r, 'q, F, C, G>
-where
-    <F as StaticVersion>::Is: Filterable<'static, 'static>,
-    'q: 'r,
-{
-    type Table = C::Entity;
-}

+ 0 - 98
microrm/src/query/resolve.rs

@@ -1,98 +0,0 @@
-use crate::{Entity, QueryInterface, WithID};
-use std::hash::Hasher;
-
-/// Any query that can be completed/executed
-pub trait Resolvable<'r, 'q, T: Entity>: super::build::QueryComponent
-where
-    'q: 'r,
-{
-    fn qi(&self) -> &'r QueryInterface<'q>;
-
-    fn exec(self) -> Result<(), crate::Error>
-    where
-        Self: Sized,
-    {
-        self.no_result()
-    }
-    fn one(self) -> Result<Option<WithID<T>>, crate::Error>
-    where
-        Self: Sized,
-    {
-        self.result()
-    }
-    fn all(self) -> Result<Vec<WithID<T>>, crate::Error>
-    where
-        Self: Sized,
-    {
-        self.results()
-    }
-
-    fn no_result(self) -> Result<(), crate::Error>
-    where
-        Self: Sized,
-    {
-        let mut hasher = std::collections::hash_map::DefaultHasher::new();
-        self.contribute(&mut hasher);
-
-        self.qi().with_cache(
-            hasher.finish(),
-            || self.qi().db.conn.prepare(self.derive().assemble()).unwrap(),
-            |stmt| {
-                self.bind(stmt)?;
-
-                self.qi().expect_no_result(stmt)
-            },
-        )
-    }
-
-    fn result(self) -> Result<Option<WithID<T>>, crate::Error>
-    where
-        Self: Sized,
-    {
-        let mut hasher = std::collections::hash_map::DefaultHasher::new();
-        self.contribute(&mut hasher);
-        self.qi().with_cache(
-            hasher.finish(),
-            || {
-                let query = self.derive().assemble();
-                self.qi().db.conn.prepare(query).unwrap()
-            },
-            |stmt| {
-                self.bind(stmt)?;
-
-                self.qi().expect_one_result(stmt, &mut |stmt| {
-                    let id: i64 = stmt.read(0)?;
-                    Ok(WithID::wrap(T::build_from(stmt)?, id))
-                })
-            },
-        )
-    }
-
-    fn results(self) -> Result<Vec<WithID<T>>, crate::Error>
-    where
-        Self: Sized,
-    {
-        let mut hasher = std::collections::hash_map::DefaultHasher::new();
-        self.contribute(&mut hasher);
-        self.qi().with_cache(
-            hasher.finish(),
-            || self.qi().db.conn.prepare(self.derive().assemble()).unwrap(),
-            |stmt| {
-                self.bind(stmt)?;
-
-                let mut res = Vec::new();
-                loop {
-                    let state = stmt.next()?;
-                    if state == sqlite::State::Done {
-                        break;
-                    }
-
-                    let id: i64 = stmt.read(0)?;
-                    res.push(WithID::wrap(T::build_from(stmt)?, id));
-                }
-
-                Ok(res)
-            },
-        )
-    }
-}

+ 0 - 52
microrm/src/query/select.rs

@@ -1,52 +0,0 @@
-use super::build::{DerivedQuery, QueryComponent, QueryPart, StaticVersion};
-use super::{Filterable, Resolvable};
-use crate::{Entity, Error, QueryInterface};
-use std::hash::{Hash, Hasher};
-use std::marker::PhantomData;
-
-pub struct Select<'r, 'q, T: Entity> {
-    qi: &'r QueryInterface<'q>,
-    _ghost: PhantomData<T>,
-}
-
-impl<'r, 'q, T: Entity> Select<'r, 'q, T> {
-    pub fn new(qi: &'r QueryInterface<'q>) -> Self {
-        Self {
-            qi,
-            _ghost: std::marker::PhantomData,
-        }
-    }
-}
-
-impl<'r, 'q, T: Entity> StaticVersion for Select<'r, 'q, T> {
-    type Is = Select<'static, 'static, T>;
-}
-
-impl<'r, 'q, T: Entity> QueryComponent for Select<'r, 'q, T> {
-    fn derive(&self) -> DerivedQuery {
-        DerivedQuery::new().add(
-            QueryPart::Root,
-            format!("SELECT * FROM `{}`", T::table_name()),
-        )
-    }
-
-    fn contribute<H: Hasher>(&self, hasher: &mut H) {
-        "select".hash(hasher);
-        std::any::TypeId::of::<T>().hash(hasher);
-    }
-
-    // next binding point is the first
-    fn bind(&self, _stmt: &mut sqlite::Statement<'_>) -> Result<usize, Error> {
-        Ok(1)
-    }
-}
-
-impl<'r, 'q, T: Entity> Filterable<'r, 'q> for Select<'r, 'q, T> {
-    type Table = T;
-}
-
-impl<'r, 'q, T: Entity> Resolvable<'r, 'q, T> for Select<'r, 'q, T> {
-    fn qi(&self) -> &'r QueryInterface<'q> {
-        self.qi
-    }
-}

+ 0 - 301
microrm/src/query/update.rs

@@ -1,301 +0,0 @@
-use super::build::{DerivedQuery, QueryComponent, QueryPart, StaticVersion};
-use super::{Filterable, Resolvable};
-use crate::entity::EntityColumn;
-use crate::model::Modelable;
-use crate::{Entity, Error, QueryInterface};
-use std::hash::{Hash, Hasher};
-use std::marker::PhantomData;
-
-pub struct Update<'r, 'q, T: Entity> {
-    qi: &'r QueryInterface<'q>,
-    _ghost: PhantomData<T>,
-}
-
-impl<'r, 'q, T: Entity> Update<'r, 'q, T> {
-    pub fn new(qi: &'r QueryInterface<'q>) -> Self {
-        Self {
-            qi,
-            _ghost: std::marker::PhantomData,
-        }
-    }
-
-    pub fn to(self, to: &'r T) -> Entire<'r, 'q, T> {
-        Entire { wrap: self, to }
-    }
-}
-
-impl<'r, 'q, T: Entity> Settable<'r, 'q> for Update<'r, 'q, T>
-where
-    'q: 'r,
-{
-    type Table = T;
-}
-
-impl<'r, 'q, T: Entity> StaticVersion for Update<'r, 'q, T> {
-    type Is = Update<'static, 'static, T>;
-}
-
-impl<'r, 'q, T: Entity> QueryComponent for Update<'r, 'q, T> {
-    fn derive(&self) -> DerivedQuery {
-        DerivedQuery::new().add(QueryPart::Root, format!("UPDATE `{}`", T::table_name()))
-    }
-
-    fn contribute<H: Hasher>(&self, hasher: &mut H) {
-        "update".hash(hasher);
-        std::any::TypeId::of::<T>().hash(hasher);
-    }
-
-    // next binding point is the first, we do nothing here
-    fn bind(&self, _stmt: &mut sqlite::Statement<'_>) -> Result<usize, Error> {
-        Ok(1)
-    }
-}
-
-impl<'r, 'q, T: Entity> Filterable<'r, 'q> for Update<'r, 'q, T> {
-    type Table = T;
-}
-
-impl<'r, 'q, T: Entity> Resolvable<'r, 'q, T> for Update<'r, 'q, T> {
-    fn qi(&self) -> &'r QueryInterface<'q> {
-        self.qi
-    }
-}
-
-pub struct Entire<'r, 'q, T: Entity> {
-    wrap: Update<'r, 'q, T>,
-    to: &'r T,
-}
-
-impl<'r, 'q, T: Entity> StaticVersion for Entire<'r, 'q, T> {
-    type Is = Entire<'static, 'static, T>;
-}
-
-impl<'r, 'q, T: Entity> QueryComponent for Entire<'r, 'q, T> {
-    fn derive(&self) -> DerivedQuery {
-        let mut dq = self.wrap.derive();
-
-        // skip ID column
-        let cols = T::columns();
-        for column in &cols[1..] {
-            dq = dq.add(QueryPart::Set, format!("`{}` = ?", column.name()));
-        }
-        dq
-    }
-
-    fn contribute<H: Hasher>(&self, hasher: &mut H) {
-        self.wrap.contribute(hasher);
-        std::any::TypeId::of::<Self::Is>().hash(hasher);
-    }
-
-    fn bind(&self, stmt: &mut sqlite::Statement<'_>) -> Result<usize, Error> {
-        let mut ind = self.wrap.bind(stmt)?;
-
-        self.to.visit_values::<Error, _>(&mut |val| {
-            val.bind_to(stmt, ind)?;
-            ind += 1;
-            Ok(())
-        })?;
-
-        Ok(ind)
-    }
-}
-
-impl<'r, 'q, T: Entity> Resolvable<'r, 'q, T> for Entire<'r, 'q, T> {
-    fn qi(&self) -> &'r QueryInterface<'q> {
-        self.wrap.qi
-    }
-}
-
-impl<'r, 'q, T: Entity> Filterable<'r, 'q> for Entire<'r, 'q, T> {
-    type Table = T;
-}
-
-pub trait Settable<'r, 'q>: Resolvable<'r, 'q, Self::Table>
-where
-    'q: 'r,
-{
-    type Table: Entity;
-
-    fn update<C: EntityColumn<Entity = Self::Table>, G: Modelable + ?Sized>(
-        self,
-        col: C,
-        given: &'r G,
-    ) -> Set<'r, 'q, Self, C, G>
-    where
-        Self: Sized,
-    {
-        Set {
-            wrap: self,
-            col,
-            given,
-            _ghost: PhantomData,
-        }
-    }
-}
-
-/// A concrete SET clause
-pub struct Set<'r, 'q, S: Settable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized>
-where
-    'q: 'r,
-{
-    wrap: S,
-    col: C,
-    given: &'r G,
-    _ghost: PhantomData<&'q ()>,
-}
-
-impl<'r, 'q, S: Settable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized> StaticVersion
-    for Set<'r, 'q, S, C, G>
-where
-    <S as StaticVersion>::Is: Settable<'static, 'static>,
-{
-    type Is = Set<'static, 'static, <S as StaticVersion>::Is, C, u64>;
-}
-
-impl<'r, 'q, S: Settable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized> QueryComponent
-    for Set<'r, 'q, S, C, G>
-where
-    <S as StaticVersion>::Is: Settable<'static, 'static>,
-    'q: 'r,
-{
-    fn derive(&self) -> DerivedQuery {
-        self.wrap
-            .derive()
-            .add(QueryPart::Set, format!("`{}` = ?", self.col.name()))
-    }
-
-    fn contribute<H: Hasher>(&self, hasher: &mut H) {
-        self.wrap.contribute(hasher);
-        std::any::TypeId::of::<Self::Is>().hash(hasher);
-        std::any::TypeId::of::<C>().hash(hasher);
-    }
-
-    fn bind(&self, stmt: &mut sqlite::Statement<'_>) -> Result<usize, crate::Error> {
-        let next_index = self.wrap.bind(stmt)?;
-
-        self.given.bind_to(stmt, next_index)?;
-
-        Ok(next_index + 1)
-    }
-}
-
-impl<'r, 'q, S: Settable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized>
-    Resolvable<'r, 'q, C::Entity> for Set<'r, 'q, S, C, G>
-where
-    <S as StaticVersion>::Is: Settable<'static, 'static>,
-    'q: 'r,
-{
-    fn qi(&self) -> &'r QueryInterface<'q> {
-        self.wrap.qi()
-    }
-}
-
-impl<'r, 'q, S: Settable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized> Settable<'r, 'q>
-    for Set<'r, 'q, S, C, G>
-where
-    <S as StaticVersion>::Is: Settable<'static, 'static>,
-    'q: 'r,
-{
-    type Table = C::Entity;
-}
-
-impl<'r, 'q, S: Settable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized> Filterable<'r, 'q>
-    for Set<'r, 'q, S, C, G>
-where
-    <S as StaticVersion>::Is: Settable<'static, 'static>,
-    'q: 'r,
-{
-    type Table = C::Entity;
-}
-
-#[cfg(test)]
-mod test {
-    use crate::prelude::*;
-    use crate::query::Resolvable;
-    use crate::test_support::KVStore;
-
-    #[test]
-    fn simple_update() {
-        let db = crate::DB::new_in_memory(crate::Schema::new().entity::<KVStore>()).unwrap();
-        let qi = db.query_interface();
-
-        qi.add(&KVStore {
-            key: "key".into(),
-            value: "value".into(),
-        })
-        .unwrap();
-        qi.add(&KVStore {
-            key: "key2".into(),
-            value: "value2".into(),
-        })
-        .unwrap();
-        qi.add(&KVStore {
-            key: "key2".into(),
-            value: "value2b".into(),
-        })
-        .unwrap();
-
-        assert_eq!(
-            qi.get()
-                .by(KVStore::Key, "key")
-                .one()
-                .unwrap()
-                .unwrap()
-                .value,
-            "value"
-        );
-        assert_eq!(qi.get().by(KVStore::Key, "key2").all().unwrap().len(), 2);
-
-        qi.update()
-            .update(KVStore::Value, "newvalue")
-            .by(KVStore::Key, "key")
-            .exec()
-            .unwrap();
-        assert_eq!(
-            qi.get()
-                .by(KVStore::Key, "key")
-                .one()
-                .unwrap()
-                .unwrap()
-                .value,
-            "newvalue"
-        );
-    }
-
-    #[test]
-    fn swapout() {
-        let db = crate::DB::new_in_memory(crate::Schema::new().entity::<KVStore>()).unwrap();
-        let qi = db.query_interface();
-
-        let id = qi
-            .add(&KVStore {
-                key: "a".into(),
-                value: "b".into(),
-            })
-            .unwrap();
-
-        let check = qi.get().by(KVStore::ID, &id).all().unwrap();
-        assert_eq!(check.len(), 1);
-        assert_eq!(check[0].key, "a");
-        assert_eq!(check[0].value, "b");
-
-        qi.update()
-            .to(&KVStore {
-                key: "c".into(),
-                value: "d".into(),
-            })
-            .by(KVStore::ID, &id)
-            .exec()
-            .unwrap();
-
-        let check = qi.get().by(KVStore::ID, &id).all().unwrap();
-        assert_eq!(check.len(), 1);
-        assert_eq!(check[0].key, "c");
-        assert_eq!(check[0].value, "d");
-
-        let check = qi.get().by_id(&id).all().unwrap();
-        assert_eq!(check.len(), 1);
-        assert_eq!(check[0].key, "c");
-        assert_eq!(check[0].value, "d");
-    }
-}

+ 13 - 43
microrm/src/schema.rs

@@ -1,53 +1,23 @@
-mod create;
+use crate::entity::{Entity, EntityVisitor};
+use crate::db::{Database,DatabaseItem,DatabaseItemVisitor};
 
-use crate::entity::{Entity, Index};
+mod entity;
 
-/// How we describe an entire schema
-#[derive(Debug)]
-pub struct Schema {
-    drop: Vec<String>,
-    create: Vec<String>,
-}
 
-impl Schema {
-    pub fn new() -> Self {
-        Self {
-            drop: Vec::new(),
-            create: Vec::new(),
-        }
-    }
 
-    pub fn entity<E: Entity>(mut self) -> Self {
-        let (drop, create) = create::sql_for_table::<E>();
-        self.drop.push(drop);
-        self.create.push(create);
-        self
-    }
+pub fn collect_from_database<DB: Database>() {
+    struct IV(entity::EntityStateContainer);
 
-    pub fn index<I: Index>(mut self) -> Self {
-        let (drop, create) = create::sql_for_index::<I>();
-        self.drop.push(drop);
-        self.create.push(create);
-        self
+    impl DatabaseItemVisitor for IV {
+        fn visit<DI: DatabaseItem>(&mut self) where Self: Sized {
+            DI::accept_entity_visitor(&mut self.0.make_context(DI::item_key()));
+        }
     }
 
-    pub fn add<E: Entity>(mut self) -> Self {
-        let (drop, create) = create::sql_for_table::<E>();
-        self.drop.push(drop);
-        self.create.push(create);
-        self
-    }
+    let mut iv = IV(entity::EntityStateContainer::default());
 
-    pub fn drop(&self) -> &Vec<String> {
-        &self.drop
-    }
-    pub fn create(&self) -> &Vec<String> {
-        &self.create
-    }
-}
+    DB::accept_item_visitor(&mut iv);
 
-impl Default for Schema {
-    fn default() -> Self {
-        Self::new()
-    }
+    println!("collected base info from database: {:?}", iv.0);
 }
+

+ 0 - 224
microrm/src/schema/create.rs

@@ -1,224 +0,0 @@
-use crate::entity::{Entity, Index};
-
-pub fn sql_for_table<T: Entity>() -> (String, String) {
-    let all_columns = T::columns();
-
-    let mut columns = vec!["id integer primary key".to_owned()];
-
-    // skip the id column type
-    for col in all_columns.iter().skip(1) {
-        columns.push(format!(
-            "\"{}\" {}{}",
-            col.name(),
-            col.sql_type(),
-            if col.fk_table_name().is_some() {
-                format!(
-                    " REFERENCES \"{}\"(\"{}\") ON DELETE CASCADE",
-                    col.fk_table_name().unwrap(),
-                    col.fk_column_name().unwrap()
-                )
-            } else {
-                "".to_owned()
-            }
-        ));
-    }
-
-    (
-        format!("DROP TABLE IF EXISTS `{}`", <T as Entity>::table_name()),
-        format!(
-            "CREATE TABLE IF NOT EXISTS `{}` ({})",
-            <T as Entity>::table_name(),
-            columns.join(",")
-        ),
-    )
-}
-
-pub fn sql_for_index<I: Index>() -> (String, String) {
-    (
-        format!("DROP INDEX IF EXISTS \"{}\"", I::index_name()),
-        format!(
-            "CREATE {}INDEX \"{}\" ON \"{}\" ({})",
-            if I::unique() { "UNIQUE " } else { "" },
-            I::index_name(),
-            I::table_name(),
-            I::column_names()
-                .iter()
-                .map(|x| format!("\"{}\"", x))
-                .collect::<Vec<_>>()
-                .join(",")
-        ),
-    )
-}
-
-#[cfg(test)]
-mod test {
-    #[derive(serde::Serialize, serde::Deserialize, crate::Entity)]
-    #[microrm_internal]
-    pub struct Empty {}
-
-    #[derive(serde::Serialize, serde::Deserialize, crate::Entity)]
-    #[microrm_internal]
-    pub struct Single {
-        e: i32,
-    }
-
-    #[derive(serde::Serialize, serde::Deserialize, crate::Entity)]
-    #[microrm_internal]
-    pub struct Reference {
-        e: SingleID,
-    }
-
-    #[test]
-    fn example_sql_for() {
-        assert_eq!(
-            super::sql_for_table::<Empty>(),
-            (
-                r#"DROP TABLE IF EXISTS "empty""#.to_owned(),
-                r#"CREATE TABLE IF NOT EXISTS "empty" (id integer primary key)"#.to_owned()
-            )
-        );
-        assert_eq!(
-            super::sql_for_table::<Single>(),
-            (
-                r#"DROP TABLE IF EXISTS "single""#.to_owned(),
-                r#"CREATE TABLE IF NOT EXISTS "single" (id integer primary key,"e" integer)"#
-                    .to_owned()
-            )
-        );
-
-        assert_eq!(
-            super::sql_for_table::<Reference>(),
-            (
-                r#"DROP TABLE IF EXISTS "reference""#.to_owned(),
-                r#"CREATE TABLE IF NOT EXISTS "reference" (id integer primary key,"e" integer)"#
-                    .to_owned()
-            )
-        );
-    }
-
-    #[derive(serde::Serialize, serde::Deserialize, crate::Modelable)]
-    #[microrm_internal]
-    pub struct Unit(u8);
-    #[derive(serde::Serialize, serde::Deserialize, crate::Entity)]
-    #[microrm_internal]
-    pub struct UnitNewtype {
-        newtype: Unit,
-    }
-
-    #[test]
-    fn unit_newtype_struct() {
-        assert_eq!(
-            super::sql_for_table::<UnitNewtype>(),
-            (
-                r#"DROP TABLE IF EXISTS "unit_newtype""#.to_owned(),
-                r#"CREATE TABLE IF NOT EXISTS "unit_newtype" (id integer primary key,"newtype" integer)"#
-                    .to_owned()
-            )
-        );
-    }
-
-    #[derive(serde::Serialize, serde::Deserialize, crate::Modelable)]
-    #[microrm_internal]
-    pub struct NonUnit(u8, u8);
-    #[derive(serde::Serialize, serde::Deserialize, crate::Entity)]
-    #[microrm_internal]
-    pub struct NonUnitNewtype {
-        newtype: NonUnit,
-    }
-
-    #[test]
-    fn nonunit_newtype_struct() {
-        assert_eq!(
-            super::sql_for_table::<NonUnitNewtype>(),
-            (
-                r#"DROP TABLE IF EXISTS "non_unit_newtype""#.to_owned(),
-                r#"CREATE TABLE IF NOT EXISTS "non_unit_newtype" (id integer primary key,"newtype" blob)"#.to_owned()
-            )
-        )
-    }
-
-    #[derive(serde::Serialize, serde::Deserialize, crate::Entity)]
-    #[microrm_internal]
-    pub struct Child {
-        #[microrm_foreign]
-        parent_id: SingleID,
-    }
-
-    #[test]
-    fn test_foreign_key() {
-        assert_eq!(
-            super::sql_for_table::<Child>(),
-            (
-                r#"DROP TABLE IF EXISTS "child""#.to_owned(),
-                r#"CREATE TABLE IF NOT EXISTS "child" (id integer primary key,"parent_id" integer REFERENCES "single"("id") ON DELETE CASCADE)"#.to_owned()
-            )
-        );
-    }
-
-    #[derive(serde::Serialize, serde::Deserialize, crate::Entity)]
-    #[microrm_internal]
-    pub struct KeyValue {
-        key: String,
-        value: String,
-    }
-
-    microrm_macros::make_index_internal!(ValueIndex, KeyValue::Value);
-    microrm_macros::make_index_internal!(!UniqueValueIndex, KeyValue::Value);
-
-    #[test]
-    fn test_indexes() {
-        assert_eq!(
-            super::sql_for_index::<ValueIndex>(),
-            (
-                r#"DROP INDEX IF EXISTS "value_index""#.to_owned(),
-                r#"CREATE INDEX "value_index" ON "key_value" ("value")"#.to_owned()
-            )
-        );
-        assert_eq!(
-            super::sql_for_index::<UniqueValueIndex>(),
-            (
-                r#"DROP INDEX IF EXISTS "unique_value_index""#.to_owned(),
-                r#"CREATE UNIQUE INDEX "unique_value_index" ON "key_value" ("value")"#.to_owned()
-            )
-        )
-    }
-
-    #[derive(serde::Serialize, serde::Deserialize, crate::Entity)]
-    #[microrm_internal]
-    pub struct VecTest {
-        e: u64,
-        test: Vec<usize>,
-    }
-
-    #[test]
-    fn test_vec() {
-        assert_eq!(
-            super::sql_for_table::<VecTest>().1,
-            r#"CREATE TABLE IF NOT EXISTS "vec_test" (id integer primary key,"e" integer,"test" blob)"#.to_owned()
-        );
-    }
-
-    #[derive(crate::Modelable, serde::Deserialize, serde::Serialize)]
-    #[microrm_internal]
-    pub enum TestEnum {
-        A,
-        B,
-        C,
-    }
-
-    #[derive(serde::Serialize, serde::Deserialize, crate::Entity)]
-    #[microrm_internal]
-    pub struct EnumContainer {
-        before: usize,
-        e: TestEnum,
-        after: usize,
-    }
-
-    #[test]
-    fn test_enum() {
-        assert_eq!(
-            super::sql_for_table::<EnumContainer>().1,
-            r#"CREATE TABLE IF NOT EXISTS "enum_container" (id integer primary key,"before" integer,"e" text,"after" integer)"#.to_owned()
-        )
-    }
-}

+ 106 - 0
microrm/src/schema/entity.rs

@@ -0,0 +1,106 @@
+use std::collections::HashMap;
+
+use crate::entity::{Entity,EntityVisitor, EntityPartVisitor, EntityPart};
+
+#[derive(Debug)]
+pub enum PartType {
+    Datum(&'static str),
+    Assoc(&'static str),
+}
+
+#[derive(Debug)]
+pub struct PartState {
+    name: &'static str,
+    ty: PartType
+}
+
+impl PartState {
+    fn build<EP: EntityPart>() -> Self {
+        PartState {
+            name: EP::part_name(),
+            ty: match EP::is_assoc() {
+                true => PartType::Assoc(EP::assoc_name()),
+                false => PartType::Datum("datum placeholder"),
+            }
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct EntityState {
+    context: &'static str,
+    name: &'static str,
+    typeid: std::any::TypeId,
+
+    parts: Vec<PartState>,
+}
+
+impl EntityState {
+    fn build<E: Entity>(context: &'static str) -> Self {
+
+        #[derive(Default)]
+        struct PartVisitor(Vec<PartState>);
+        impl EntityPartVisitor for PartVisitor {
+            fn visit<EP: EntityPart>(&mut self) {
+                self.0.push(PartState::build::<EP>());
+            }
+        }
+
+        let mut pv = PartVisitor::default();
+        E::accept_part_visitor(&mut pv);
+
+        Self {
+            context,
+            name: E::entity_name(),
+            typeid: std::any::TypeId::of::<E>(),
+            parts: pv.0,
+        }
+    }
+}
+
+#[derive(Default, Debug)]
+pub struct EntityStateContainer {
+    states: HashMap<(&'static str, &'static str), EntityState>,
+}
+
+impl EntityStateContainer {
+    pub fn make_context(&mut self, context: &'static str) -> EntityContext {
+        EntityContext {
+            context,
+            container: self
+        }
+    }
+}
+
+pub struct EntityContext<'a> {
+    context: &'static str,
+    container: &'a mut EntityStateContainer,
+}
+
+impl<'a> EntityVisitor for EntityContext<'a> {
+    fn visit<E: Entity>(&mut self) {
+        println!("visiting entity in context");
+        let entry = self.container.states.entry((self.context, E::entity_name()));
+        // three cases:
+        // 1. we haven't seen this entity in this context before
+        // 2. we've seen this entity in this context before
+        // 3. we haven't seen this entity in this context before, but we've seen one with an identical name in the same context
+
+        let entry = entry.or_insert_with(|| EntityState::build::<E>(self.context));
+        // sanity-check
+        if entry.typeid != std::any::TypeId::of::<E>() {
+            panic!("Identical entity name but different typeid!");
+        }
+
+        struct RecursiveVisitor<'a, 'b>(&'a mut EntityContext<'b>);
+        impl<'a, 'b> EntityPartVisitor for RecursiveVisitor<'a, 'b> {
+            fn visit<EP: EntityPart>(&mut self) {
+                if EP::is_assoc() {
+                    EP::visit_assoc(self.0)
+                }
+            }
+        }
+
+        E::accept_part_visitor(&mut RecursiveVisitor(self));
+    }
+}