Procházet zdrojové kódy

Swap from rusqlite crate to sqlite crate and introduce QueryInterface.

Kestrel před 2 roky
rodič
revize
c452df2598

+ 34 - 111
Cargo.lock

@@ -2,29 +2,12 @@
 # It is not intended for manual editing.
 version = 3
 
-[[package]]
-name = "ahash"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
-dependencies = [
- "getrandom",
- "once_cell",
- "version_check",
-]
-
 [[package]]
 name = "base64"
 version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
 
-[[package]]
-name = "bitflags"
-version = "1.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
-
 [[package]]
 name = "block-buffer"
 version = "0.10.2"
@@ -34,6 +17,12 @@ dependencies = [
  "generic-array",
 ]
 
+[[package]]
+name = "cc"
+version = "1.0.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
+
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
@@ -75,18 +64,6 @@ dependencies = [
  "crypto-common",
 ]
 
-[[package]]
-name = "fallible-iterator"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
-
-[[package]]
-name = "fallible-streaming-iterator"
-version = "0.1.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
-
 [[package]]
 name = "generic-array"
 version = "0.14.5"
@@ -97,35 +74,6 @@ dependencies = [
  "version_check",
 ]
 
-[[package]]
-name = "getrandom"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
-dependencies = [
- "cfg-if",
- "libc",
- "wasi",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.11.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
-dependencies = [
- "ahash",
-]
-
-[[package]]
-name = "hashlink"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf"
-dependencies = [
- "hashbrown",
-]
-
 [[package]]
 name = "itoa"
 version = "1.0.1"
@@ -138,33 +86,17 @@ version = "0.2.125"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b"
 
-[[package]]
-name = "libsqlite3-sys"
-version = "0.24.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14"
-dependencies = [
- "pkg-config",
- "vcpkg",
-]
-
-[[package]]
-name = "memchr"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
-
 [[package]]
 name = "microrm"
 version = "0.1.0"
 dependencies = [
  "base64",
  "microrm-macros",
- "rusqlite",
  "serde",
  "serde_bytes",
  "serde_json",
  "sha2",
+ "sqlite",
 ]
 
 [[package]]
@@ -177,12 +109,6 @@ dependencies = [
  "syn",
 ]
 
-[[package]]
-name = "once_cell"
-version = "1.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
-
 [[package]]
 name = "pkg-config"
 version = "0.3.25"
@@ -207,21 +133,6 @@ dependencies = [
  "proc-macro2",
 ]
 
-[[package]]
-name = "rusqlite"
-version = "0.27.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85127183a999f7db96d1a976a309eebbfb6ea3b0b400ddd8340190129de6eb7a"
-dependencies = [
- "bitflags",
- "fallible-iterator",
- "fallible-streaming-iterator",
- "hashlink",
- "libsqlite3-sys",
- "memchr",
- "smallvec",
-]
-
 [[package]]
 name = "ryu"
 version = "1.0.9"
@@ -280,10 +191,34 @@ dependencies = [
 ]
 
 [[package]]
-name = "smallvec"
-version = "1.8.0"
+name = "sqlite"
+version = "0.26.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
+checksum = "3fb1a534c07ec276fbbe0e55a1c00814d8563da3a2f4d9d9d4c802bd1278db6a"
+dependencies = [
+ "libc",
+ "sqlite3-sys",
+]
+
+[[package]]
+name = "sqlite3-src"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a260b07ce75a0644c6f5891f34f46db9869e731838e95293469ab17999abcfa3"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
+[[package]]
+name = "sqlite3-sys"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04d2f028faeb14352df7934b4771806f60d61ce61be1928ec92396d7492e2e54"
+dependencies = [
+ "libc",
+ "sqlite3-src",
+]
 
 [[package]]
 name = "syn"
@@ -308,20 +243,8 @@ version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
 
-[[package]]
-name = "vcpkg"
-version = "0.2.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
-
 [[package]]
 name = "version_check"
 version = "0.9.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
-
-[[package]]
-name = "wasi"
-version = "0.10.2+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"

+ 20 - 8
microrm-macros/src/lib.rs

@@ -151,9 +151,13 @@ pub fn derive_entity(tokens: TokenStream) -> TokenStream {
             fn raw_id(&self) -> i64 { self.0 }
         }
 
-        impl #microrm_ref::re_export::rusqlite::ToSql for #id_name {
-            fn to_sql(&self) -> #microrm_ref::re_export::rusqlite::Result<#microrm_ref::re_export::rusqlite::types::ToSqlOutput<'_>> {
-                self.0.to_sql()
+        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: &sqlite::Statement, col_offset: usize) -> sqlite::Result<(Self, usize)> where Self: Sized {
+                stmt.read::<i64>(col_offset).map(|x| (#id_name(x), 1))
             }
         }
 
@@ -176,7 +180,7 @@ pub fn derive_entity(tokens: TokenStream) -> TokenStream {
                     #(#field_names),*
                 }
             }
-            fn values(&self) -> Vec<&dyn #microrm_ref::re_export::rusqlite::ToSql> {
+            fn values(&self) -> Vec<&dyn #microrm_ref::model::Modelable> {
                 vec![ #(#value_references),* ]
             }
 
@@ -200,10 +204,18 @@ pub fn derive_modelable(tokens: TokenStream) -> TokenStream {
     let ident = input.ident;
 
     quote!{
-        impl #microrm_ref::re_export::rusqlite::ToSql for #ident {
-            fn to_sql(&self) -> #microrm_ref::re_export::rusqlite::Result<#microrm_ref::re_export::rusqlite::types::ToSqlOutput<'_>> {
-                use #microrm_ref::re_export::rusqlite::types::{ToSqlOutput,Value};
-                Ok(ToSqlOutput::Owned(Value::Text(#microrm_ref::re_export::serde_json::to_string(self).expect("can be serialized"))))
+        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))
             }
         }
     }.into()

+ 1 - 1
microrm/Cargo.toml

@@ -8,7 +8,7 @@ edition = "2021"
 [dependencies]
 base64 = "0.13"
 sha2 = "0.10"
-rusqlite = "0.27"
+sqlite = "0.26"
 serde = { version = "1.0", features = ["derive"] }
 serde_bytes = { version = "0.11.6" }
 serde_json = { version = "1.0" }

+ 62 - 35
microrm/src/lib.rs

@@ -25,13 +25,14 @@
 //!
 //! let schema = microrm::model::SchemaModel::new().add::<KVStore>();
 //! let db = microrm::DB::new_in_memory(schema).unwrap();
+//! let qi = microrm::query::QueryInterface::new(&db);
 //!
-//! microrm::query::add(&db, &KVStore {
+//! qi.add(&KVStore {
 //!     key: "a_key".to_string(),
 //!     value: "a_value".to_string()
 //! });
 //!
-//! let qr = microrm::query::get_one_by(&db, KVStoreColumns::Key, "a_key");
+//! let qr = qi.get_one_by(KVStoreColumns::Key, "a_key");
 //!
 //! assert_eq!(qr.is_some(), true);
 //! assert_eq!(qr.as_ref().unwrap().key, "a_key");
@@ -42,19 +43,22 @@ mod meta;
 pub mod model;
 pub mod query;
 
+use meta::Metaschema;
 pub use microrm_macros::{Entity, Modelable};
+use model::Entity;
 
 // no need to show the re-exports in the documentation
 #[doc(hidden)]
 pub mod re_export {
-    pub use rusqlite;
     pub use serde;
     pub use serde_json;
+    pub use sqlite;
 }
 
 #[derive(Debug)]
 pub enum DBError {
     ConnectFailure,
+    EarlyFailure(sqlite::Error),
     NoSchema,
     DifferentSchema,
     DropFailure,
@@ -62,6 +66,16 @@ pub enum DBError {
     SanityCheckFailure,
 }
 
+#[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))
@@ -72,7 +86,7 @@ impl std::error::Error for DBError {}
 
 /// SQLite database connection
 pub struct DB {
-    conn: rusqlite::Connection,
+    conn: sqlite::Connection,
     schema_hash: String,
     schema: model::SchemaModel,
 }
@@ -81,21 +95,21 @@ impl DB {
     pub fn new(
         schema: model::SchemaModel,
         path: &str,
-        allow_recreate: bool,
+        mode: CreateMode
     ) -> Result<Self, DBError> {
         Self::from_connection(
-            rusqlite::Connection::open(path).map_err(|_| DBError::ConnectFailure)?,
+            sqlite::Connection::open(path).map_err(|_| DBError::ConnectFailure)?,
             schema,
-            allow_recreate,
+            mode,
         )
     }
 
     /// 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> {
         Self::from_connection(
-            rusqlite::Connection::open_in_memory().map_err(|_| DBError::ConnectFailure)?,
+            sqlite::Connection::open(":memory:").map_err(|_| DBError::ConnectFailure)?,
             schema,
-            true,
+            CreateMode::AllowNewDatabase,
         )
     }
 
@@ -104,9 +118,9 @@ impl DB {
     }
 
     fn from_connection(
-        conn: rusqlite::Connection,
+        conn: sqlite::Connection,
         schema: model::SchemaModel,
-        allow_recreate: bool,
+        mode: CreateMode,
     ) -> Result<Self, DBError> {
         let sig = Self::calculate_schema_hash(&schema);
         let ret = Self {
@@ -114,7 +128,7 @@ impl DB {
             schema_hash: sig,
             schema: schema.add::<meta::Metaschema>(),
         };
-        ret.check_schema(allow_recreate)?;
+        ret.check_schema(mode)?;
         Ok(ret)
     }
 
@@ -136,43 +150,54 @@ impl DB {
         base64::encode(hasher.finalize())
     }
 
-    fn check_schema(&self, allow_recreate: bool) -> Result<(), DBError> {
-        let hash = query::get_one_by(self, meta::MetaschemaColumns::Key, "schema_hash");
+    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| {
+            println!("row: {:?}", row);
+            has_metaschema = true;
+            true
+        }).map_err(|e| DBError::EarlyFailure(e))?;
+
+        if mode != CreateMode::MustExist {
+            println!("Creating schema!");
+            return self.create_schema()
+        }
+
+        let qi = query::QueryInterface::new(self);
+        let hash = qi.get_one_by(meta::MetaschemaColumns::Key, "schema_hash");
+        // let hash = query::get_one_by(self, meta::MetaschemaColumns::Key, "schema_hash");
 
         if hash.is_none() {
-            if !allow_recreate {
+            if mode == CreateMode::MustExist {
                 return Err(DBError::NoSchema);
             }
-            self.create_schema()?;
+            return self.create_schema();
         } else if hash.unwrap().value != self.schema_hash {
-            if !allow_recreate {
+            if mode != CreateMode::AllowSchemaUpdate {
                 return Err(DBError::DifferentSchema);
             }
-            self.create_schema()?;
+            self.drop_schema()?;
+            return self.create_schema();
         }
 
         Ok(())
     }
 
-    fn create_schema(&self) -> Result<(), DBError> {
+    fn drop_schema(&self) -> Result<(), DBError> {
         for ds in self.schema.drop() {
-            let prepared = self.conn.prepare(ds);
-            prepared
-                .unwrap()
-                .execute([])
-                .map_err(|_| DBError::DropFailure)?;
+            self.conn.execute(ds).map_err(|_| DBError::DropFailure)?;
         }
+        Ok(())
+    }
 
+    fn create_schema(&self) -> Result<(), DBError> {
         for cs in self.schema.create() {
-            let prepared = self.conn.prepare(cs);
-            prepared
-                .unwrap()
-                .execute([])
-                .map_err(|_| DBError::CreateFailure)?;
+            self.conn.execute(cs).map_err(|_| DBError::CreateFailure)?;
         }
 
-        let add_result = query::add(
-            self,
+        let qi = query::QueryInterface::new(self);
+
+        let add_result = qi.add(
             &meta::Metaschema {
                 key: "schema_hash".to_string(),
                 value: self.schema_hash.clone(),
@@ -181,7 +206,7 @@ impl DB {
 
         assert!(add_result.is_some());
 
-        let sanity_check = query::get_one_by(self, meta::MetaschemaColumns::Key, "schema_hash");
+        let sanity_check = qi.get_one_by(meta::MetaschemaColumns::Key, "schema_hash");
         assert!(sanity_check.is_some());
         assert_eq!(sanity_check.unwrap().value, self.schema_hash);
 
@@ -206,6 +231,7 @@ mod test {
     #[test]
     fn in_memory_schema() {
         let _db = DB::new_in_memory(simple_schema());
+        drop(_db);
     }
 
     #[derive(serde::Serialize, serde::Deserialize, crate::Entity)]
@@ -219,10 +245,11 @@ mod test {
     fn simple_foreign_key() {
         let db = DB::new_in_memory(super::model::SchemaModel::new().add::<S1>().add::<S2>())
             .expect("Can't connect to in-memory DB");
+        let qi = crate::query::QueryInterface::new(&db);
 
-        let id = crate::query::add(&db, &S1 { an_id: -1 }).expect("Can't add S1");
-        let child_id = crate::query::add(&db, &S2 { parent_id: id }).expect("Can't add S2");
+        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");
 
-        crate::query::get_one_by_id::<S2>(&db, child_id).expect("Can't get S2 instance");
+        qi.get_one_by_id(child_id).expect("Can't get S2 instance");
     }
 }

+ 18 - 9
microrm/src/model.rs

@@ -2,21 +2,25 @@ mod create;
 pub(crate) mod load;
 pub(crate) mod store;
 
+// Modelable implementations
+mod modelable;
+
 #[derive(Debug)]
 pub enum ModelError {
-    DBError(rusqlite::Error),
+    DBError(sqlite::Error),
     LoadError(String),
+    StoreError(String),
     EmptyStoreError,
     CreateError,
 }
 
-impl From<rusqlite::Error> for ModelError {
-    fn from(e: rusqlite::Error) -> Self {
+impl From<sqlite::Error> for ModelError {
+    fn from(e: sqlite::Error) -> Self {
         Self::DBError(e)
     }
 }
 
-impl From<ModelError> for rusqlite::Error {
+impl From<ModelError> for sqlite::Error {
     fn from(e: ModelError) -> Self {
         match e {
             ModelError::DBError(e) => e,
@@ -33,7 +37,7 @@ impl std::fmt::Display for ModelError {
 
 impl serde::ser::Error for ModelError {
     fn custom<T: std::fmt::Display>(msg: T) -> Self {
-        Self::LoadError(format!("{}", msg))
+        Self::StoreError(format!("{}", msg))
     }
 }
 
@@ -45,6 +49,12 @@ impl serde::de::Error for ModelError {
 
 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<()>;
+    fn build_from(stmt: &sqlite::Statement, col_offset: usize) -> sqlite::Result<(Self, usize)> where Self: Sized;
+}
+
 /// A database entity, aka a struct representing a row in a table
 pub trait Entity: for<'de> serde::Deserialize<'de> + serde::Serialize {
     type Column: EntityColumns + 'static + Copy;
@@ -59,19 +69,18 @@ pub trait Entity: for<'de> serde::Deserialize<'de> + serde::Serialize {
     fn name(c: Self::Column) -> &'static str
     where
         Self: Sized;
-    fn values(&self) -> Vec<&dyn rusqlite::ToSql>;
+    fn values(&self) -> Vec<&dyn Modelable>;
 
     fn foreign_keys() -> &'static [&'static dyn EntityForeignKey<Self::Column>];
 }
 
 /// Trait representing the columns of a database entity
-pub trait EntityColumns: PartialEq + From<usize>
-{
+pub trait EntityColumns: PartialEq + From<usize> {
     type Entity: Entity;
 }
 
 /// Trait for entity IDs in the database
-pub trait EntityID: std::fmt::Debug + Copy + crate::re_export::rusqlite::ToSql {
+pub trait EntityID: std::fmt::Debug + Copy + Modelable {
     type Entity: Entity;
     fn from_raw_id(raw: i64) -> Self;
     fn raw_id(&self) -> i64;

+ 6 - 8
microrm/src/model/create.rs

@@ -181,7 +181,7 @@ pub fn sql_for<T: crate::model::Entity>() -> (String, String) {
             <T as crate::model::Entity>::table_name()
         ),
         format!(
-            "CREATE TABLE \"{}\" ({})",
+            "CREATE TABLE IF NOT EXISTS \"{}\" ({})",
             <T as crate::model::Entity>::table_name(),
             columns.join(",")
         ),
@@ -190,8 +190,6 @@ pub fn sql_for<T: crate::model::Entity>() -> (String, String) {
 
 #[cfg(test)]
 mod test {
-    use serde::Deserialize;
-
     #[derive(serde::Serialize, serde::Deserialize, crate::Entity)]
     #[microrm_internal]
     pub struct Empty {}
@@ -214,14 +212,14 @@ mod test {
             super::sql_for::<Empty>(),
             (
                 r#"DROP TABLE IF EXISTS "empty""#.to_owned(),
-                r#"CREATE TABLE "empty" (id integer primary key)"#.to_owned()
+                r#"CREATE TABLE IF NOT EXISTS "empty" (id integer primary key)"#.to_owned()
             )
         );
         assert_eq!(
             super::sql_for::<Single>(),
             (
                 r#"DROP TABLE IF EXISTS "single""#.to_owned(),
-                r#"CREATE TABLE "single" (id integer primary key,"e" integer)"#.to_owned()
+                r#"CREATE TABLE IF NOT EXISTS "single" (id integer primary key,"e" integer)"#.to_owned()
             )
         );
 
@@ -229,7 +227,7 @@ mod test {
             super::sql_for::<Reference>(),
             (
                 r#"DROP TABLE IF EXISTS "reference""#.to_owned(),
-                r#"CREATE TABLE "reference" (id integer primary key,"e" integer)"#.to_owned()
+                r#"CREATE TABLE IF NOT EXISTS "reference" (id integer primary key,"e" integer)"#.to_owned()
             )
         );
     }
@@ -249,7 +247,7 @@ mod test {
             super::sql_for::<UnitNewtype>(),
             (
                 r#"DROP TABLE IF EXISTS "unit_newtype""#.to_owned(),
-                r#"CREATE TABLE "unit_newtype" (id integer primary key,"newtype" integer)"#
+                r#"CREATE TABLE IF NOT EXISTS "unit_newtype" (id integer primary key,"newtype" integer)"#
                     .to_owned()
             )
         );
@@ -283,7 +281,7 @@ mod test {
             super::sql_for::<Child>(),
             (
                 r#"DROP TABLE IF EXISTS "child""#.to_owned(),
-                r#"CREATE TABLE "child" (id integer primary key,"parent_id" integer references "single"("id"))"#.to_owned()
+                r#"CREATE TABLE IF NOT EXISTS "child" (id integer primary key,"parent_id" integer references "single"("id"))"#.to_owned()
             )
         );
     }

+ 28 - 22
microrm/src/model/load.rs

@@ -1,20 +1,19 @@
 use serde::de::Visitor;
+use super::Modelable;
 
-use rusqlite::Row;
-
-pub struct RowDeserializer<'de> {
-    row: &'de Row<'de>,
+pub struct RowDeserializer<'de, 'l> {
+    row: &'de sqlite::Statement<'l>,
     col_index: usize,
 }
 
-impl<'de> RowDeserializer<'de> {
-    pub fn from_row(row: &'de Row) -> Self {
+impl<'de, 'l> RowDeserializer<'de, 'l> {
+    pub fn from_row(row: &'de sqlite::Statement<'l>) -> Self {
         // we skip the rowid by starting at index 1
         Self { row, col_index: 1 }
     }
 }
 
-impl<'de, 'a> serde::de::Deserializer<'de> for &'a mut RowDeserializer<'de> {
+impl<'de, 'a, 'l> serde::de::Deserializer<'de> for &'a mut RowDeserializer<'de, 'l> {
     type Error = super::ModelError;
 
     fn deserialize_any<V: Visitor<'de>>(self, _v: V) -> Result<V::Value, Self::Error> {
@@ -22,45 +21,52 @@ impl<'de, 'a> serde::de::Deserializer<'de> for &'a mut RowDeserializer<'de> {
     }
 
     fn deserialize_bool<V: Visitor<'de>>(self, v: V) -> Result<V::Value, Self::Error> {
-        let res = v.visit_bool(self.row.get(self.col_index)?);
+        todo!()
+        /*let res = v.visit_bool(self.row.get(self.col_index)?);
         self.col_index += 1;
-        res
+        res*/
     }
 
     fn deserialize_i8<V: Visitor<'de>>(self, v: V) -> Result<V::Value, Self::Error> {
-        let res = v.visit_i8(self.row.get(self.col_index)?);
+        todo!()
+        /*let res = v.visit_i8(self.row.get(self.col_index)?);
         self.col_index += 1;
-        res
+        res*/
     }
 
     fn deserialize_i16<V: Visitor<'de>>(self, v: V) -> Result<V::Value, Self::Error> {
-        let res = v.visit_i16(self.row.get(self.col_index)?);
+        todo!()
+        /*let res = v.visit_i16(self.row.get(self.col_index)?);
         self.col_index += 1;
-        res
+        res*/
     }
 
     fn deserialize_i32<V: Visitor<'de>>(self, v: V) -> Result<V::Value, Self::Error> {
-        let res = v.visit_i32(self.row.get(self.col_index)?);
+        todo!()
+        /*let res = v.visit_i32(self.row.get(self.col_index)?);
         self.col_index += 1;
-        res
+        res*/
     }
 
     fn deserialize_i64<V: Visitor<'de>>(self, v: V) -> Result<V::Value, Self::Error> {
-        let res = v.visit_i64(self.row.get(self.col_index)?);
-        self.col_index += 1;
+        let built = Modelable::build_from(self.row, self.col_index).map_err(|e| Self::Error::LoadError(e.to_string()))?;
+        let res = v.visit_i64(built.0);
+        self.col_index += built.1;
         res
     }
 
     fn deserialize_string<V: Visitor<'de>>(self, v: V) -> Result<V::Value, Self::Error> {
-        let res = v.visit_string(self.row.get(self.col_index)?);
-        self.col_index += 1;
+        let built = Modelable::build_from(self.row, self.col_index).map_err(|e| Self::Error::LoadError(e.to_string()))?;
+        let res = v.visit_string(built.0);
+        self.col_index += built.1;
         res
     }
 
     fn deserialize_byte_buf<V: Visitor<'de>>(self, v: V) -> Result<V::Value, Self::Error> {
-        let res = v.visit_byte_buf(self.row.get(self.col_index)?);
+        todo!()
+        /*let res = v.visit_byte_buf(self.row.get(self.col_index)?);
         self.col_index += 1;
-        res
+        res*/
     }
 
     fn deserialize_struct<V: Visitor<'de>>(
@@ -87,7 +93,7 @@ impl<'de, 'a> serde::de::Deserializer<'de> for &'a mut RowDeserializer<'de> {
     }
 }
 
-impl<'de> serde::de::SeqAccess<'de> for RowDeserializer<'de> {
+impl<'de, 'l> serde::de::SeqAccess<'de> for RowDeserializer<'de, 'l> {
     type Error = super::ModelError;
 
     fn next_element_seed<T: serde::de::DeserializeSeed<'de>>(

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

@@ -0,0 +1,38 @@
+use super::Modelable;
+use sqlite::Bindable;
+
+impl Modelable for i32 {
+    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 i32, 1))
+    }
+}
+
+impl Modelable for i64 {
+    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))
+    }
+}
+
+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!");
+    }
+}
+
+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))
+    }
+}

+ 12 - 2
microrm/src/model/store.rs

@@ -1,5 +1,15 @@
 use super::Entity;
 
-pub fn serialize_as_row<T: Entity>(value: &T) -> Vec<&dyn rusqlite::ToSql> {
-    value.values()
+pub fn serialize_into<T: Entity>(stmt: &mut sqlite::Statement, value: &T) -> sqlite::Result<()> {
+    let mut i = 1;
+    for c in &value.values() {
+        c.bind_to(stmt, i)?;
+        i += 1;
+    }
+
+    Ok(())
 }
+
+/*pub fn serialize_as_row<T: Entity>(value: &T) -> Vec<&dyn rusqlite::ToSql> {
+    value.values()
+}*/

+ 151 - 67
microrm/src/query.rs

@@ -1,7 +1,7 @@
 use crate::model::{Entity, EntityColumns, EntityID};
 use crate::DB;
 
-pub mod condition;
+// pub mod condition;
 
 /// Wraps an entity with its ID, for example as a query result.
 ///
@@ -47,36 +47,157 @@ impl<T: Entity> std::ops::DerefMut for WithID<T> {
     }
 }
 
-/// Search for an entity by a property
-pub fn get_one_by<T: Entity<Column = C>, C: EntityColumns<Entity = T>, V: rusqlite::ToSql>(
-    db: &DB,
-    c: C,
-    val: V,
-) -> Option<WithID<T>> {
-    let table_name = <T as Entity>::table_name();
-    let column_name = <T as Entity>::name(c);
+pub struct QueryInterface<'l> {
+    db: &'l crate::DB,
+}
 
-    let mut prepared = db
-        .conn
-        .prepare(&format!(
-            "SELECT * FROM \"{}\" WHERE \"{}\" = ?1",
-            table_name, column_name
-        ))
-        .ok()?;
+impl<'l> QueryInterface<'l> {
+    pub fn new(db: &'l crate::DB) -> Self {
+        Self { db }
+    }
 
-    let result = prepared.query_row([&val], |row| {
-        let mut deser = crate::model::load::RowDeserializer::from_row(row);
-        Ok(WithID::wrap(
-            T::deserialize(&mut deser).expect("deserialization works"),
-            row.get(0).expect("can get id"),
-        ))
-    });
+    /// Helper function to process an expected one result
+    fn expect_one_result<T>(&self, stmt: &mut sqlite::Statement, with_result: &mut dyn FnMut(&mut sqlite::Statement) -> Option<T>) -> Option<T> {
+        let state = stmt.next();
+        assert!(state.is_ok());
+        assert_eq!(state.ok(), Some(sqlite::State::Row));
+
+        let res = with_result(stmt);
+        
+        let state = stmt.next();
+        assert!(state.is_ok());
+        assert_eq!(state.ok(), Some(sqlite::State::Done));
+
+        res
+    }
+
+    /// Search for an entity by a property
+    pub fn get_one_by<T: Entity<Column = C>, C: EntityColumns<Entity = T>, V: crate::model::Modelable>(
+        &self,
+        c: C,
+        val: V,
+    ) -> Option<WithID<T>> {
+        let table_name = <T as Entity>::table_name();
+        let column_name = <T as Entity>::name(c);
+
+        let mut prepared = self.db
+            .conn
+            .prepare(&format!(
+                "SELECT * FROM \"{}\" WHERE \"{}\" = ?",
+                table_name, column_name
+            ))
+            .expect("");
+
+        prepared.reset().ok()?;
+
+        val.bind_to(&mut prepared, 1).ok()?;
+
+        return self.expect_one_result(&mut prepared, &mut |stmt| {
+            let id: i64 = stmt.read(0).ok()?;
+            let mut rd = crate::model::load::RowDeserializer::from_row(&stmt);
+            return Some(WithID::wrap(T::deserialize(&mut rd).ok()?, id))
+        })
+    }
+
+    /// Search for an entity by ID
+    pub fn get_one_by_id<I: EntityID<Entity=T>, T: Entity>(&self, id: I) -> Option<WithID<T>> {
+        let table_name = <T as Entity>::table_name();
+        let mut prepared = self.db
+            .conn
+            .prepare(&format!("SELECT * FROM \"{}\" WHERE id = ?", table_name))
+            .ok()?;
+
+        id.bind_to(&mut prepared, 1).ok()?;
+
+        return self.expect_one_result(&mut prepared, &mut |stmt| {
+            let id: i64 = stmt.read(0).ok()?;
+            let mut rd = crate::model::load::RowDeserializer::from_row(&stmt);
+            return Some(WithID::wrap(T::deserialize(&mut rd).ok()?, id))
+        })
+    }
+
+    /// Search for all entities matching a property
+    pub fn get_all_by<T: Entity<Column = C>, C: EntityColumns<Entity = T>, V: crate::model::Modelable>(
+        &self,
+        c: C,
+        val: V,
+    ) -> Option<Vec<WithID<T>>> {
+        let table_name = <T as Entity>::table_name();
+        let column_name = <T as Entity>::name(c);
+
+        let mut prepared = self.db
+            .conn
+            .prepare(&format!(
+                "SELECT * FROM \"{}\" WHERE \"{}\" = ?",
+                table_name, column_name
+            ))
+            .ok()?;
+
+        val.bind_to(&mut prepared, 1).ok()?;
 
-    result.ok()
+        todo!();
+
+        /*let rows = prepared
+            .query_map([&val], |row| {
+                let mut deser = crate::model::load::RowDeserializer::from_row(row);
+                Ok(WithID::wrap(
+                    T::deserialize(&mut deser)?,
+                    row.get(0).expect("can get rowid"),
+                ))
+            })
+            .ok()?;
+
+        Some(rows.map(|x| x.unwrap()).collect())*/
+    }
+
+    /// Add an entity to its table
+    pub fn add<T: Entity + serde::Serialize>(&self, m: &T) -> Option<<T as Entity>::ID> {
+
+        let placeholders = (0..(<T as Entity>::column_count() - 1))
+            .map(|n| "?".to_string())
+            .collect::<Vec<_>>()
+            .join(",");
+
+        let mut prepared = self.db.conn.prepare(&format!(
+            "INSERT INTO \"{}\" VALUES (NULL, {}) RETURNING \"id\"",
+            <T as Entity>::table_name(),
+            placeholders
+        )).ok()?;
+
+        crate::model::store::serialize_into(&mut prepared, m).ok()?;
+
+        let rowid = self.expect_one_result(&mut prepared, &mut |stmt| {
+            stmt.read::<i64>(0).ok()
+        })?;
+
+        Some(<T as Entity>::ID::from_raw_id(rowid))
+
+        /*
+        let row = crate::model::store::serialize_as_row(m);
+
+        let placeholders = (0..(<T as Entity>::column_count() - 1))
+            .map(|n| format!("?{}", n + 1))
+            .collect::<Vec<_>>()
+            .join(",");
+
+        let res = db.conn.prepare(&format!(
+            "INSERT INTO \"{}\" VALUES (NULL, {})",
+            <T as Entity>::table_name(),
+            placeholders
+        ));
+        let mut prepared = res.ok()?;
+
+        // make sure we bound enough things (not including ID column here)
+        assert_eq!(row.len(), <T as Entity>::column_count() - 1);
+        */
+
+        /*let id = prepared.insert(rusqlite::params_from_iter(row)).ok()?;
+        Some(<T as Entity>::ID::from_raw_id(id))*/
+    }
 }
 
 /// Search for all entities matching a property
-pub fn get_all_by<T: Entity<Column = C>, C: EntityColumns<Entity = T>, V: rusqlite::ToSql>(
+pub fn get_all_by<T: Entity<Column = C>, C: EntityColumns<Entity = T>, V: sqlite::Bindable>(
     db: &DB,
     c: C,
     val: V,
@@ -84,6 +205,10 @@ pub fn get_all_by<T: Entity<Column = C>, C: EntityColumns<Entity = T>, V: rusqli
     let table_name = <T as Entity>::table_name();
     let column_name = <T as Entity>::name(c);
 
+    todo!();
+
+    /*
+
     let mut prepared = db
         .conn
         .prepare(&format!(
@@ -103,46 +228,5 @@ pub fn get_all_by<T: Entity<Column = C>, C: EntityColumns<Entity = T>, V: rusqli
         .ok()?;
 
     Some(rows.map(|x| x.unwrap()).collect())
-}
-
-/// Search for an entity by ID
-pub fn get_one_by_id<T: Entity>(db: &DB, id: <T as Entity>::ID) -> Option<WithID<T>> {
-    let table_name = <T as Entity>::table_name();
-    let mut prepared = db
-        .conn
-        .prepare(&format!("SELECT * FROM \"{}\" WHERE id = ?1", table_name))
-        .ok()?;
-
-    let result = prepared.query_row([&id], |row| {
-        let mut deser = crate::model::load::RowDeserializer::from_row(row);
-        Ok(WithID::wrap(
-            T::deserialize(&mut deser).expect("deserialization works"),
-            row.get(0).expect("can get rowid"),
-        ))
-    });
-
-    result.ok()
-}
-
-/// Add an entity to its table
-pub fn add<T: Entity + serde::Serialize>(db: &DB, m: &T) -> Option<<T as Entity>::ID> {
-    let row = crate::model::store::serialize_as_row(m);
-
-    let placeholders = (0..(<T as Entity>::column_count() - 1))
-        .map(|n| format!("?{}", n + 1))
-        .collect::<Vec<_>>()
-        .join(",");
-
-    let res = db.conn.prepare(&format!(
-        "INSERT INTO \"{}\" VALUES (NULL, {})",
-        <T as Entity>::table_name(),
-        placeholders
-    ));
-    let mut prepared = res.ok()?;
-
-    // make sure we bound enough things (not including ID column here)
-    assert_eq!(row.len(), <T as Entity>::column_count() - 1);
-
-    let id = prepared.insert(rusqlite::params_from_iter(row)).ok()?;
-    Some(<T as Entity>::ID::from_raw_id(id))
+    */
 }