Browse Source

Rearranging and small breaking interface changes.

Kestrel 2 years ago
parent
commit
542999297d

+ 19 - 2
Cargo.lock

@@ -338,7 +338,7 @@ dependencies = [
 
 [[package]]
 name = "microrm"
-version = "0.2.3"
+version = "0.2.4"
 dependencies = [
  "base64",
  "criterion",
@@ -355,14 +355,31 @@ dependencies = [
 
 [[package]]
 name = "microrm-macros"
-version = "0.2.0"
+version = "0.2.1"
 dependencies = [
  "convert_case",
+ "nom",
  "proc-macro2",
  "quote",
  "syn",
 ]
 
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "nom"
+version = "7.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
 [[package]]
 name = "num-traits"
 version = "0.2.15"

+ 2 - 2
README.md

@@ -26,8 +26,8 @@ pub struct KVStore {
 // the !KVStoreIndex here means a type representing a unique index named KVStoreIndex
 make_index!(!KVStoreIndex, KVStoreColumns::Key);
 
-let schema = microrm::model::SchemaModel::new()
-    .add::<KVStore>()
+let schema = microrm::Schema::new()
+    .entity::<KVStore>()
     .index::<KVStoreIndex>();
 
 // dump the schema in case you want to inspect it manually

+ 3 - 1
microrm-macros/Cargo.toml

@@ -12,8 +12,10 @@ description = "Procedural macro implementations for the microrm crate."
 proc-macro = true
 
 [dependencies]
-# proc_macro = "*"
 syn = { version = "1.0", features = ["derive", "extra-traits"] }
 quote = "1.0"
 convert_case = "0.5"
 proc-macro2 = "1.0"
+
+# SQL DSL parsing
+nom = "7.1"

+ 201 - 0
microrm-macros/src/entity.rs

@@ -0,0 +1,201 @@
+use proc_macro::TokenStream;
+use quote::{format_ident, quote};
+use syn::{parse_macro_input, DeriveInput};
+
+use convert_case::{Case, Casing};
+
+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
+}
+
+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 enum_name = format_ident!("{}Columns", &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!"),
+    };
+
+    let mut variants = Vec::new();
+    let mut field_names = Vec::new();
+    let mut field_numbers = Vec::new();
+    let mut field_types = Vec::new();
+    let mut value_references = Vec::new();
+
+    let mut foreign_keys = Vec::new();
+    let mut foreign_key_impls = Vec::new();
+
+    let mut build_clauses = Vec::new();
+
+    let mut index: usize = 0;
+
+    for name in fields.named.iter() {
+        let converted_case =
+            format!("{}", name.ident.as_ref().unwrap().clone()).to_case(Case::UpperCamel);
+        let converted_case = format_ident!("{}", converted_case);
+        variants.push(converted_case.clone());
+
+        let field_name = name.ident.as_ref().unwrap().clone();
+        let field_name_str = format!("{}", field_name);
+        field_names.push(quote! { Self::Column::#converted_case => #field_name_str });
+
+        let nn = field_numbers.len() + 1;
+        field_numbers.push(quote! { #nn => Self::#converted_case, });
+
+        let ty = &name.ty;
+        field_types.push(quote! { <#ty as #microrm_ref::model::Modelable>::column_type() });
+
+        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: #enum_name::#converted_case }
+            });
+            foreign_key_impls.push(quote!{
+                struct #fk_struct_name {
+                    col: #enum_name
+                }
+                impl #microrm_ref::entity::EntityForeignKey<#enum_name> for #fk_struct_name {
+                    fn local_column(&self) -> &#enum_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"
+                    }
+                }
+            });
+        }
+
+        value_references.push(quote! { &self. #field_name });
+
+        index += 1;
+        build_clauses.push(quote! { #field_name: <#ty as #microrm_ref::model::Modelable>::build_from(stmt, #index)?.0 });
+    }
+
+    let column_types_name = format_ident!(
+        "{}_COLUMN_TYPES",
+        struct_name.to_string().to_case(Case::ScreamingSnake)
+    );
+
+    let field_count = fields.named.iter().count();
+
+    quote!{
+        // Related types for #struct_name
+        #[derive(Clone,Copy,PartialEq,Hash)]
+        #[allow(unused)]
+        #[repr(usize)]
+        pub enum #enum_name {
+            ID,
+            #(#variants),*
+        }
+
+        #[derive(Debug,PartialEq,Clone,Copy,#microrm_ref::re_export::serde::Serialize,#microrm_ref::re_export::serde::Deserialize)]
+        #[allow(unused)]
+        pub struct #id_name (i64);
+
+        // Implementations for related types
+        impl #microrm_ref::entity::EntityColumns for #enum_name {
+            type Entity = #struct_name;
+        }
+
+        impl std::convert::From<usize> for #enum_name {
+            fn from(i: usize) -> Self {
+                match i {
+                    0 => Self::ID,
+                    #(#field_numbers)*
+                    _ => {
+                        panic!("Given invalid usize to convert to column")
+                    },
+                }
+            }
+        }
+
+        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 #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"
+            }
+        }
+        #microrm_ref::re_export::lazy_static::lazy_static!{
+            static ref #column_types_name: [&'static str;#field_count + 1] = {
+                [
+                    "id",
+                    #(#field_types),*
+                ]
+            };
+        }
+
+        // Implementations for #struct_name
+        impl #microrm_ref::entity::Entity for #struct_name {
+            type Column = #enum_name;
+            type ID = #id_name;
+
+            fn table_name() -> &'static str { #table_name }
+            fn column_count() -> usize {
+                // +1 for ID column
+                #field_count + 1
+            }
+            fn index(c: Self::Column) -> usize {
+                c as usize
+            }
+            fn name(c: Self::Column) -> &'static str {
+                match c {
+                    Self::Column::ID => "ID",
+                    #(#field_names),*
+                }
+            }
+            fn values(&self) -> Vec<&dyn #microrm_ref::model::Modelable> {
+                vec![ #(#value_references),* ]
+            }
+
+            fn build_from(stmt: &#microrm_ref::re_export::sqlite::Statement) -> #microrm_ref::re_export::sqlite::Result<Self> {
+                Ok(Self {
+                    #(#build_clauses),*
+                })
+            }
+
+            fn column_types() -> &'static [&'static str] {
+                #column_types_name.as_ref()
+            }
+            fn foreign_keys() -> &'static [&'static dyn #microrm_ref::entity::EntityForeignKey<Self::Column>] {
+                &[#(#foreign_keys),*]
+            }
+        }
+
+        // Foreign key struct implementations
+        #(#foreign_key_impls)*
+    }.into()
+}

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

@@ -0,0 +1,70 @@
+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::TypePath, 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 first_col = input.columns.first().unwrap();
+    let mut column_type_path = first_col.path.clone();
+
+    // remove variant name
+    column_type_path.segments.pop();
+    let last = column_type_path
+        .segments
+        .pop()
+        .expect("Full path to EntityColumn variant");
+    column_type_path.segments.push(last.value().clone());
+
+    let index_entity_type_name = format_ident!("{}Entity", index_struct_name);
+    let columns = input.columns.clone().into_iter();
+
+    let index_sql_name = format!("{}", index_struct_name).to_case(Case::Snake);
+
+    let unique = input.unique.is_some();
+
+    quote!{
+        pub struct #index_struct_name {}
+        type #index_entity_type_name = <#column_type_path as #microrm_ref::entity::EntityColumns>::Entity;
+
+        impl #microrm_ref::entity::Index for #index_struct_name {
+            type IndexedEntity = #index_entity_type_name;
+            fn index_name() -> &'static str {
+                #index_sql_name
+            }
+            fn columns() -> &'static [#column_type_path] where Self: Sized {
+                &[#(#columns),*]
+            }
+            fn unique() -> bool where Self: Sized {
+                #unique
+            }
+        }
+    }.into()
+}

+ 14 - 385
microrm-macros/src/lib.rs

@@ -1,8 +1,9 @@
 use proc_macro::TokenStream;
-use quote::{format_ident, quote};
-use syn::{parse_macro_input, DeriveInput};
+use quote::quote;
 
-use convert_case::{Case, Casing};
+mod entity;
+mod index;
+mod modelable;
 
 fn parse_microrm_ref(attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
     for attr in attrs {
@@ -19,22 +20,10 @@ fn parse_microrm_ref(attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
     quote! { ::microrm }
 }
 
-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
-}
-
 /// Turns a serializable/deserializable struct into a microrm entity model.
 ///
 /// There are two important visible effects:
-/// - Provides an implementation of `microrm::model::Entity`
+/// - Provides an implementation of `microrm::entity::Entity`
 /// - Defines a <struct-name>Columns enum
 ///
 /// Note that names are converted from CamelCase to snake_case and vice versa
@@ -50,377 +39,13 @@ fn parse_fk(attrs: &[syn::Attribute]) -> bool {
 /// - `#[microrm_foreign]`: this is a foreign key (and the field must be of a type implementing `EntityID`)
 #[proc_macro_derive(Entity, attributes(microrm_internal, microrm_foreign))]
 pub fn derive_entity(tokens: TokenStream) -> TokenStream {
-    let input = parse_macro_input!(tokens as DeriveInput);
-
-    let microrm_ref = parse_microrm_ref(&input.attrs);
-
-    let struct_name = &input.ident;
-    let enum_name = format_ident!("{}Columns", &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!"),
-    };
-
-    let mut variants = Vec::new();
-    let mut field_names = Vec::new();
-    let mut field_numbers = Vec::new();
-    let mut field_types = Vec::new();
-    let mut value_references = Vec::new();
-
-    let mut foreign_keys = Vec::new();
-    let mut foreign_key_impls = Vec::new();
-
-    let mut build_clauses = Vec::new();
-
-    let mut index : usize = 0;
-
-    for name in fields.named.iter() {
-        let converted_case =
-            format!("{}", name.ident.as_ref().unwrap().clone()).to_case(Case::UpperCamel);
-        let converted_case = format_ident!("{}", converted_case);
-        variants.push(converted_case.clone());
-
-        let field_name = name.ident.as_ref().unwrap().clone();
-        let field_name_str = format!("{}", field_name);
-        field_names.push(quote! { Self::Column::#converted_case => #field_name_str });
-
-        let nn = field_numbers.len() + 1;
-        field_numbers.push(quote! { #nn => Self::#converted_case, });
-
-        let ty = &name.ty;
-        field_types.push(quote!{ <#ty as #microrm_ref::model::Modelable>::column_type() });
-
-        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: #enum_name::#converted_case }
-            });
-            foreign_key_impls.push(quote!{
-                struct #fk_struct_name {
-                    col: #enum_name
-                }
-                impl #microrm_ref::model::EntityForeignKey<#enum_name> for #fk_struct_name {
-                    fn local_column(&self) -> &#enum_name { &self.col }
-                    fn foreign_table_name(&self) -> &'static str {
-                        <<#ty as #microrm_ref::model::EntityID>::Entity as #microrm_ref::model::Entity>::table_name()
-                    }
-                    fn foreign_column_name(&self) -> &'static str {
-                        "id"
-                    }
-                }
-            });
-        }
-
-        value_references.push(quote! { &self. #field_name });
-
-        index += 1;
-        build_clauses.push(quote! { #field_name: <#ty as #microrm_ref::model::Modelable>::build_from(stmt, #index)?.0 });
-    }
-
-    let column_types_name = format_ident!("{}_COLUMN_TYPES", struct_name.to_string().to_case(Case::ScreamingSnake));
-
-    let field_count = fields.named.iter().count();
-
-    quote!{
-        // Related types for #struct_name
-        #[derive(Clone,Copy,PartialEq,Hash)]
-        #[allow(unused)]
-        #[repr(usize)]
-        pub enum #enum_name {
-            ID,
-            #(#variants),*
-        }
-
-        #[derive(Debug,PartialEq,Clone,Copy,#microrm_ref::re_export::serde::Serialize,#microrm_ref::re_export::serde::Deserialize)]
-        #[allow(unused)]
-        pub struct #id_name (i64);
-
-        // Implementations for related types
-        impl #microrm_ref::model::EntityColumns for #enum_name {
-            type Entity = #struct_name;
-        }
-
-        impl std::convert::From<usize> for #enum_name {
-            fn from(i: usize) -> Self {
-                match i {
-                    0 => Self::ID,
-                    #(#field_numbers)*
-                    _ => {
-                        panic!("Given invalid usize to convert to column")
-                    },
-                }
-            }
-        }
-
-        impl #microrm_ref::model::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 #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"
-            }
-        }
-        #microrm_ref::re_export::lazy_static::lazy_static!{
-            static ref #column_types_name: [&'static str;#field_count + 1] = {
-                [
-                    "id",
-                    #(#field_types),*
-                ]
-            };
-        }
-
-        // Implementations for #struct_name
-        impl #microrm_ref::model::Entity for #struct_name {
-            type Column = #enum_name;
-            type ID = #id_name;
-
-            fn table_name() -> &'static str { #table_name }
-            fn column_count() -> usize {
-                // +1 for ID column
-                #field_count + 1
-            }
-            fn index(c: Self::Column) -> usize {
-                c as usize
-            }
-            fn name(c: Self::Column) -> &'static str {
-                match c {
-                    Self::Column::ID => "ID",
-                    #(#field_names),*
-                }
-            }
-            fn values(&self) -> Vec<&dyn #microrm_ref::model::Modelable> {
-                vec![ #(#value_references),* ]
-            }
-
-            fn build_from(stmt: &#microrm_ref::re_export::sqlite::Statement) -> #microrm_ref::re_export::sqlite::Result<Self> {
-                Ok(Self {
-                    #(#build_clauses),*
-                })
-            }
-
-            fn column_types() -> &'static [&'static str] {
-                #column_types_name.as_ref()
-            }
-            fn foreign_keys() -> &'static [&'static dyn #microrm_ref::model::EntityForeignKey<Self::Column>] {
-                &[#(#foreign_keys),*]
-            }
-        }
-
-        // Foreign key struct implementations
-        #(#foreign_key_impls)*
-    }.into()
+    entity::derive(tokens)
 }
 
 /// Marks a struct or enum as able to be directly used in an Entity to correspond to a single database column.
 #[proc_macro_derive(Modelable, attributes(microrm_internal))]
 pub fn derive_modelable(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 = 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 = 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 = 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()
-}
-
-type ColumnList = syn::punctuated::Punctuated<syn::TypePath, 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)?,
-        })
-    }
-}
-
-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 first_col = input.columns.first().unwrap();
-    let mut column_type_path = first_col.path.clone();
-
-    // remove variant name
-    column_type_path.segments.pop();
-    let last = column_type_path
-        .segments
-        .pop()
-        .expect("Full path to EntityColumn variant");
-    column_type_path.segments.push(last.value().clone());
-
-    let index_entity_type_name = format_ident!("{}Entity", index_struct_name);
-    let columns = input.columns.clone().into_iter();
-
-    let index_sql_name = format!("{}", index_struct_name).to_case(Case::Snake);
-
-    let unique = input.unique.is_some();
-
-    quote!{
-        pub struct #index_struct_name {}
-        type #index_entity_type_name = <#column_type_path as #microrm_ref::model::EntityColumns>::Entity;
-
-        impl #microrm_ref::model::Index for #index_struct_name {
-            type IndexedEntity = #index_entity_type_name;
-            fn index_name() -> &'static str {
-                #index_sql_name
-            }
-            fn columns() -> &'static [#column_type_path] where Self: Sized {
-                &[#(#columns),*]
-            }
-            fn unique() -> bool where Self: Sized {
-                #unique
-            }
-        }
-    }.into()
+    modelable::derive(tokens)
 }
 
 /// Defines a struct to represent a optionally-unique index on a table.
@@ -446,13 +71,17 @@ fn do_make_index(tokens: TokenStream, microrm_ref: proc_macro2::TokenStream) ->
 /// ```
 #[proc_macro]
 pub fn make_index(tokens: TokenStream) -> TokenStream {
-    do_make_index(tokens, quote! { ::microrm })
+    index::do_make_index(tokens, quote! { ::microrm })
 }
 
 /// For internal use inside the microrm library. See `make_index`.
 #[proc_macro]
 pub fn make_index_internal(tokens: TokenStream) -> TokenStream {
-    do_make_index(tokens, quote! { crate })
+    index::do_make_index(tokens, quote! { crate })
 }
 
-// , attributes(microrm_internal))]
+/*#[proc_macro]
+pub fn query(tokens: TokenStream) -> TokenStream {
+    query::do_query(tokens)
+}
+*/

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

@@ -0,0 +1,130 @@
+use proc_macro::TokenStream;
+use quote::{format_ident, 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()
+}

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

@@ -0,0 +1,83 @@
+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()
+}

+ 17 - 8
microrm/benches/simple_in_memory.rs

@@ -1,15 +1,15 @@
 use criterion::{black_box, criterion_group, criterion_main, Criterion};
 
-use microrm::{make_index,Entity};
-use serde::{Serialize, Deserialize};
+use microrm::{make_index, Entity};
 use rand::prelude::*;
+use serde::{Deserialize, Serialize};
 
 #[global_allocator]
 static GLOBAL: &stats_alloc::StatsAlloc<std::alloc::System> = &stats_alloc::INSTRUMENTED_SYSTEM;
 
-#[derive(Entity,serde::Serialize,serde::Deserialize)]
+#[derive(Entity, serde::Serialize, serde::Deserialize)]
 pub struct RowIDIndexed {
-    val: usize
+    val: usize,
 }
 
 fn rowid_indexed(c: &mut Criterion) {
@@ -25,17 +25,26 @@ fn rowid_indexed(c: &mut Criterion) {
 
     qi.get_one_by_id(RowIDIndexedID(0));
 
-    println!("allocations for single get_one_by_id: {}", reg.change_and_reset().allocations);
+    println!(
+        "allocations for single get_one_by_id: {}",
+        reg.change_and_reset().allocations
+    );
 
-    c.bench_function("select 0", |b| b.iter(|| qi.get_one_by_id(RowIDIndexedID(0))));
-    c.bench_function("select 0 no statement cache", |b| b.iter(|| db.query_interface().get_one_by_id(RowIDIndexedID(0))));
+    c.bench_function("select 0", |b| {
+        b.iter(|| qi.get_one_by_id(RowIDIndexedID(0)))
+    });
+    c.bench_function("select 0 no statement cache", |b| {
+        b.iter(|| db.query_interface().get_one_by_id(RowIDIndexedID(0)))
+    });
 
     fn random_id() -> RowIDIndexedID {
         let res = RowIDIndexedID((rand::random::<usize>() % 10000) as i64);
         res
     }
 
-    c.bench_function("select random", |b| b.iter(|| qi.get_one_by_id(random_id())));
+    c.bench_function("select random", |b| {
+        b.iter(|| qi.get_one_by_id(random_id()))
+    });
 }
 
 criterion_group!(benches, rowid_indexed);

+ 61 - 0
microrm/src/entity.rs

@@ -0,0 +1,61 @@
+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 Column: EntityColumns + 'static + Copy;
+    type ID: EntityID;
+    fn table_name() -> &'static str;
+    fn column_count() -> usize
+    where
+        Self: Sized;
+    fn column_types() -> &'static [&'static str]
+    where
+        Self: Sized;
+    fn index(c: Self::Column) -> usize
+    where
+        Self: Sized;
+    fn name(c: Self::Column) -> &'static str
+    where
+        Self: Sized;
+    fn values(&self) -> Vec<&dyn Modelable>;
+
+    fn build_from(stmt: &sqlite::Statement) -> sqlite::Result<Self>
+    where
+        Self: Sized;
+
+    fn foreign_keys() -> &'static [&'static dyn EntityForeignKey<Self::Column>];
+}
+
+/// Trait representing the columns of a database entity
+pub trait EntityColumns: 'static + PartialEq + From<usize> + std::hash::Hash + Clone {
+    type Entity: Entity;
+}
+
+/// 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;
+}
+
+/// Trait for a foreign key relationship
+pub trait EntityForeignKey<T: EntityColumns> {
+    fn local_column(&self) -> &T;
+    fn foreign_table_name(&self) -> &'static str;
+    fn foreign_column_name(&self) -> &'static str;
+}
+
+/// Trait for an index over a column
+pub trait Index {
+    type IndexedEntity: Entity;
+
+    fn index_name() -> &'static str
+    where
+        Self: Sized;
+    fn columns() -> &'static [<<Self as Index>::IndexedEntity as Entity>::Column]
+    where
+        Self: Sized;
+    fn unique() -> bool
+    where
+        Self: Sized;
+}

+ 43 - 0
microrm/src/error.rs

@@ -0,0 +1,43 @@
+#[derive(Debug)]
+pub enum Error {
+    DBError(sqlite::Error),
+    LoadError(String),
+    StoreError(String),
+    EmptyStoreError,
+    CreateError,
+}
+
+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 {}

+ 30 - 12
microrm/src/lib.rs

@@ -1,19 +1,25 @@
 #![doc = include_str!("../README.md")]
 
+mod error;
 mod meta;
 pub mod model;
 pub mod query;
+pub mod entity;
+pub mod schema;
 
 use meta::Metaschema;
-use model::Entity;
+use entity::Entity;
 
 pub use microrm_macros::{make_index, Entity, Modelable};
+
+pub use schema::Schema;
 pub use query::{QueryInterface, WithID};
+pub use error::Error;
 
 #[macro_export]
 macro_rules! value_list {
     ( $( $element:expr ),* ) => {
-        [ $( ($element) as &dyn $crate::model::Modelable ),* ]
+        [ $( &($element) as &dyn $crate::model::Modelable ),* ]
     }
 }
 
@@ -59,11 +65,11 @@ impl std::error::Error for DBError {}
 pub struct DB {
     conn: sqlite::Connection,
     schema_hash: String,
-    schema: model::SchemaModel,
+    schema: schema::Schema,
 }
 
 impl DB {
-    pub fn new(schema: model::SchemaModel, path: &str, mode: CreateMode) -> Result<Self, DBError> {
+    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,
@@ -72,7 +78,7 @@ impl DB {
     }
 
     /// Mostly for use in tests, but may be useful in some applications as well.
-    pub fn new_in_memory(schema: model::SchemaModel) -> Result<Self, DBError> {
+    pub fn new_in_memory(schema: schema::Schema) -> Result<Self, DBError> {
         Self::from_connection(
             sqlite::Connection::open(":memory:").map_err(|_| DBError::ConnectFailure)?,
             schema,
@@ -91,7 +97,7 @@ impl DB {
 
     fn from_connection(
         conn: sqlite::Connection,
-        schema: model::SchemaModel,
+        schema: schema::Schema,
         mode: CreateMode,
     ) -> Result<Self, DBError> {
         let sig = Self::calculate_schema_hash(&schema);
@@ -104,7 +110,7 @@ impl DB {
         Ok(ret)
     }
 
-    fn calculate_schema_hash(schema: &model::SchemaModel) -> String {
+    fn calculate_schema_hash(schema: &schema::Schema) -> String {
         use sha2::Digest;
 
         let mut hasher = sha2::Sha256::new();
@@ -264,8 +270,8 @@ mod test {
         an_id: i32,
     }
 
-    fn simple_schema() -> super::model::SchemaModel {
-        super::model::SchemaModel::new().add::<S1>()
+    fn simple_schema() -> crate::Schema {
+        crate::Schema::new().add::<S1>()
     }
 
     #[test]
@@ -283,7 +289,7 @@ mod test {
 
     #[test]
     fn simple_foreign_key() {
-        let db = DB::new_in_memory(super::model::SchemaModel::new().add::<S1>().add::<S2>())
+        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();
 
@@ -310,7 +316,7 @@ mod test2 {
 
     #[test]
     fn dump_test() {
-        let schema = crate::model::SchemaModel::new()
+        let schema = crate::Schema::new()
             .add::<KVStore>()
             .index::<KVStoreIndex>();
 
@@ -347,7 +353,7 @@ mod delete_test {
 
     #[test]
     fn delete_test() {
-        let schema = crate::model::SchemaModel::new()
+        let schema = crate::Schema::new()
             .entity::<KVStore>();
 
         let db = crate::DB::new_in_memory(schema).unwrap();
@@ -395,3 +401,15 @@ mod delete_test {
         assert_eq!(one.unwrap().value, "a_value");
     }
 }
+
+/*
+#[cfg(test)]
+mod query_macro_test {
+    #[test]
+    fn simple_select() {
+        microrm_macros::query!{
+            SELECT A,B,C FROM Something WHERE ID = ?
+        }
+    }
+}
+*/

+ 0 - 154
microrm/src/model.rs

@@ -1,53 +1,8 @@
-mod create;
 pub(crate) mod store;
 
 // Modelable implementations
 mod modelable;
 
-#[derive(Debug)]
-pub enum ModelError {
-    DBError(sqlite::Error),
-    LoadError(String),
-    StoreError(String),
-    EmptyStoreError,
-    CreateError,
-}
-
-impl From<sqlite::Error> for ModelError {
-    fn from(e: sqlite::Error) -> Self {
-        Self::DBError(e)
-    }
-}
-
-impl From<ModelError> for sqlite::Error {
-    fn from(e: ModelError) -> Self {
-        match e {
-            ModelError::DBError(e) => e,
-            _ => panic!(),
-        }
-    }
-}
-
-impl std::fmt::Display for ModelError {
-    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
-        fmt.write_fmt(format_args!("{:?}", self))
-    }
-}
-
-impl serde::ser::Error for ModelError {
-    fn custom<T: std::fmt::Display>(msg: T) -> Self {
-        Self::StoreError(format!("{}", msg))
-    }
-}
-
-impl serde::de::Error for ModelError {
-    fn custom<T: std::fmt::Display>(msg: T) -> Self {
-        Self::LoadError(format!("{}", msg))
-    }
-}
-
-impl std::error::Error for ModelError {}
-
 /// 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<()>;
@@ -59,112 +14,3 @@ pub trait Modelable {
         Self: Sized;
 }
 
-/// A database entity, aka a struct representing a row in a table
-pub trait Entity: 'static + for<'de> serde::Deserialize<'de> + serde::Serialize {
-    type Column: EntityColumns + 'static + Copy;
-    type ID: EntityID;
-    fn table_name() -> &'static str;
-    fn column_count() -> usize
-    where
-        Self: Sized;
-    fn column_types() -> &'static [&'static str]
-    where
-        Self: Sized;
-    fn index(c: Self::Column) -> usize
-    where
-        Self: Sized;
-    fn name(c: Self::Column) -> &'static str
-    where
-        Self: Sized;
-    fn values(&self) -> Vec<&dyn Modelable>;
-
-    fn build_from(stmt: &sqlite::Statement) -> sqlite::Result<Self>
-    where
-        Self: Sized;
-
-    fn foreign_keys() -> &'static [&'static dyn EntityForeignKey<Self::Column>];
-}
-
-/// Trait representing the columns of a database entity
-pub trait EntityColumns: 'static + PartialEq + From<usize> + std::hash::Hash + Clone {
-    type Entity: Entity;
-}
-
-/// 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;
-}
-
-/// Trait for a foreign key relationship
-pub trait EntityForeignKey<T: EntityColumns> {
-    fn local_column(&self) -> &T;
-    fn foreign_table_name(&self) -> &'static str;
-    fn foreign_column_name(&self) -> &'static str;
-}
-
-/// Trait for an index over a column
-pub trait Index {
-    type IndexedEntity: Entity;
-
-    fn index_name() -> &'static str
-    where
-        Self: Sized;
-    fn columns() -> &'static [<<Self as Index>::IndexedEntity as Entity>::Column]
-    where
-        Self: Sized;
-    fn unique() -> bool
-    where
-        Self: Sized;
-}
-
-/// How we describe an entire schema
-#[derive(Debug)]
-pub struct SchemaModel {
-    drop: Vec<String>,
-    create: Vec<String>,
-}
-
-impl SchemaModel {
-    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 index<I: Index>(mut self) -> Self {
-        let (drop, create) = create::sql_for_index::<I>();
-        self.drop.push(drop);
-        self.create.push(create);
-        self
-    }
-
-    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
-    }
-
-    pub fn drop(&self) -> &Vec<String> {
-        &self.drop
-    }
-    pub fn create(&self) -> &Vec<String> {
-        &self.create
-    }
-}
-
-impl Default for SchemaModel {
-    fn default() -> Self {
-        Self::new()
-    }
-}

+ 1 - 1
microrm/src/model/store.rs

@@ -1,4 +1,4 @@
-use super::Entity;
+use crate::entity::Entity;
 
 pub fn serialize_into<T: Entity>(stmt: &mut sqlite::Statement, value: &T) -> sqlite::Result<()> {
     let mut i = 1;

+ 4 - 2
microrm/src/query.rs

@@ -1,6 +1,8 @@
-use crate::model::{Entity, EntityColumns, EntityID};
+use crate::entity::{Entity, EntityColumns, EntityID};
 
+// pub mod expr;
 // pub mod condition;
+// pub mod builder;
 
 /// Wraps an entity with its ID, for example as a query result.
 ///
@@ -133,7 +135,7 @@ impl<'l> QueryInterface<'l> {
         with(query)
     }
 
-    fn cached_query_column<Column: crate::model::EntityColumns, Return>(
+    fn cached_query_column<Column: EntityColumns, Return>(
         &self,
         context: &'static str,
         ty: std::any::TypeId,

+ 36 - 0
microrm/src/query/builder.rs

@@ -0,0 +1,36 @@
+use crate::{Entity, model::Modelable};
+
+use std::marker::PhantomData;
+
+pub struct Builder { }
+
+impl Builder {
+    pub fn get<T: Entity>() -> Select<T> { Select { _ghost: PhantomData } }
+}
+
+pub struct Select<T: Entity> {
+    _ghost: PhantomData<T>,
+}
+
+/*impl<T: Entity> Select<T> {
+}*/
+
+pub trait Selectable {
+    type Table: Entity;
+
+    fn given<G: Modelable>(self, column: <Self::Table as Entity>::Column, given: G) -> SelectGiven<Self, Self::Table, G> where Self: Sized;
+    // fn given_id(self, id: <Self::Table as Entity>::ID) -> SelectGiven<Table = Self::Table> where Self: Sized;
+}
+
+pub struct SelectGiven<S: Selectable<Table = T>, T: Entity, Given: Modelable> {
+    column: <T as Entity>::Column,
+    _ghost: (PhantomData<S>, PhantomData<Given>)
+}
+
+impl<S: Selectable<Table = T>, T: Entity, Given: Modelable> Selectable for SelectGiven<S, T, Given> {
+    type Table = T;
+
+    fn given<G: Modelable>(self, column: <Self::Table as Entity>::Column, given: G) -> SelectGiven<Self, Self::Table, G> where Self: Sized {
+        todo!()
+    }
+}

+ 23 - 0
microrm/src/query/expr.rs

@@ -0,0 +1,23 @@
+use crate::{Entity,model::EntityColumns};
+
+pub struct Join<Source: Entity, Search: Entity> {
+    _source: std::marker::PhantomData<Source>,
+    _search: std::marker::PhantomData<Search>,
+}
+
+/*pub enum Expr<Over: EntityColumns> {
+    ColumnExpr(Over, Expr<Over>),
+}*/
+
+/*pub struct Select<T: Entity> {
+    _mark: std::marker::PhantomData<T>,
+    // columns: 
+    // where_clause: Option<Expr<<T as Entity>::Column>>
+}*/
+
+/*impl<T: Entity> Select<T> {
+    fn where(self) -> Self {
+        
+    }
+}*/
+

+ 53 - 0
microrm/src/schema.rs

@@ -0,0 +1,53 @@
+mod create;
+
+use crate::entity::{Entity,Index};
+
+/// 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 index<I: Index>(mut self) -> Self {
+        let (drop, create) = create::sql_for_index::<I>();
+        self.drop.push(drop);
+        self.create.push(create);
+        self
+    }
+
+    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
+    }
+
+    pub fn drop(&self) -> &Vec<String> {
+        &self.drop
+    }
+    pub fn create(&self) -> &Vec<String> {
+        &self.create
+    }
+}
+
+impl Default for Schema {
+    fn default() -> Self {
+        Self::new()
+    }
+}

+ 5 - 4
microrm/src/model/create.rs → microrm/src/schema/create.rs

@@ -1,4 +1,6 @@
-pub fn sql_for_table<T: crate::model::Entity>() -> (String, String) {
+use crate::entity::Entity;
+
+pub fn sql_for_table<T: Entity>() -> (String, String) {
     let types = T::column_types();
 
     let mut columns = vec!["id integer primary key".to_owned()];
@@ -31,18 +33,17 @@ pub fn sql_for_table<T: crate::model::Entity>() -> (String, String) {
     (
         format!(
             "DROP TABLE IF EXISTS \"{}\"",
-            <T as crate::model::Entity>::table_name()
+            <T as Entity>::table_name()
         ),
         format!(
             "CREATE TABLE IF NOT EXISTS \"{}\" ({})",
-            <T as crate::model::Entity>::table_name(),
+            <T as Entity>::table_name(),
             columns.join(",")
         ),
     )
 }
 
 pub fn sql_for_index<I: super::Index>() -> (String, String) {
-    use super::Entity;
     (
         format!("DROP INDEX IF EXISTS \"{}\"", I::index_name()),
         format!(