|
@@ -46,34 +46,98 @@ impl<T: Entity> std::ops::DerefMut for WithID<T> {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+type CacheIndex = (&'static str, std::any::TypeId, 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.
|
|
pub struct QueryInterface<'l> {
|
|
pub struct QueryInterface<'l> {
|
|
db: &'l crate::DB,
|
|
db: &'l crate::DB,
|
|
|
|
+
|
|
|
|
+ cache: std::sync::Mutex<std::collections::HashMap<CacheIndex, sqlite::Statement<'l>>>,
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+const NO_HASH: u64 = 0;
|
|
|
|
+
|
|
impl<'l> QueryInterface<'l> {
|
|
impl<'l> QueryInterface<'l> {
|
|
pub fn new(db: &'l crate::DB) -> Self {
|
|
pub fn new(db: &'l crate::DB) -> Self {
|
|
- Self { db }
|
|
|
|
|
|
+ Self {
|
|
|
|
+ db,
|
|
|
|
+ cache: std::sync::Mutex::new(std::collections::HashMap::new()),
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- /// Helper function to process an expected one result
|
|
|
|
|
|
+ /// Helper function to process an expected single result
|
|
|
|
+ /// Note that this errors out if there is more than a single result
|
|
fn expect_one_result<T>(
|
|
fn expect_one_result<T>(
|
|
&self,
|
|
&self,
|
|
stmt: &mut sqlite::Statement,
|
|
stmt: &mut sqlite::Statement,
|
|
with_result: &mut dyn FnMut(&mut sqlite::Statement) -> Option<T>,
|
|
with_result: &mut dyn FnMut(&mut sqlite::Statement) -> Option<T>,
|
|
) -> Option<T> {
|
|
) -> Option<T> {
|
|
- let state = stmt.next();
|
|
|
|
- assert!(state.is_ok());
|
|
|
|
- assert_eq!(state.ok(), Some(sqlite::State::Row));
|
|
|
|
|
|
+ let state = stmt.next().ok()?;
|
|
|
|
+ if state != sqlite::State::Row {
|
|
|
|
+ return None;
|
|
|
|
+ }
|
|
|
|
|
|
let res = with_result(stmt);
|
|
let res = with_result(stmt);
|
|
|
|
|
|
- let state = stmt.next();
|
|
|
|
- assert!(state.is_ok());
|
|
|
|
- assert_eq!(state.ok(), Some(sqlite::State::Done));
|
|
|
|
|
|
+ let state = stmt.next().ok()?;
|
|
|
|
+ if state != sqlite::State::Done {
|
|
|
|
+ return None;
|
|
|
|
+ }
|
|
|
|
|
|
res
|
|
res
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ fn cached_query<Return>(
|
|
|
|
+ &self,
|
|
|
|
+ context: &'static str,
|
|
|
|
+ ty: std::any::TypeId,
|
|
|
|
+ create: &dyn Fn() -> sqlite::Statement<'l>,
|
|
|
|
+ with: &mut dyn FnMut(&mut sqlite::Statement<'l>) -> Return,
|
|
|
|
+ ) -> Return {
|
|
|
|
+ let mut cache = self.cache.lock().expect("Couldn't acquire cache?");
|
|
|
|
+ let key = (context, ty, NO_HASH);
|
|
|
|
+ if !cache.contains_key(&key) {
|
|
|
|
+ cache.insert(key, create());
|
|
|
|
+ }
|
|
|
|
+ let mut query = cache
|
|
|
|
+ .get_mut(&key)
|
|
|
|
+ .expect("Just-inserted item not in cache?");
|
|
|
|
+
|
|
|
|
+ query.reset().expect("Couldn't reset query");
|
|
|
|
+ with(&mut query)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fn cached_query_column<Column: crate::model::EntityColumns, Return>(
|
|
|
|
+ &self,
|
|
|
|
+ context: &'static str,
|
|
|
|
+ ty: std::any::TypeId,
|
|
|
|
+ variant: &Column,
|
|
|
|
+ create: &dyn Fn() -> sqlite::Statement<'l>,
|
|
|
|
+ with: &mut dyn FnMut(&mut sqlite::Statement<'l>) -> Return,
|
|
|
|
+ ) -> Return {
|
|
|
|
+ use std::hash::Hasher;
|
|
|
|
+
|
|
|
|
+ let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
|
|
|
+ variant.hash(&mut hasher);
|
|
|
|
+ let hash = hasher.finish();
|
|
|
|
+
|
|
|
|
+ let mut cache = self.cache.lock().expect("Couldn't acquire cache?");
|
|
|
|
+ let key = (context, ty, hash);
|
|
|
|
+ if !cache.contains_key(&key) {
|
|
|
|
+ cache.insert(key, create());
|
|
|
|
+ }
|
|
|
|
+ let mut query = cache
|
|
|
|
+ .get_mut(&key)
|
|
|
|
+ .expect("Just-inserted item not in cache?");
|
|
|
|
+
|
|
|
|
+ query.reset().expect("Couldn't reset query");
|
|
|
|
+ with(&mut query)
|
|
|
|
+ }
|
|
|
|
+
|
|
/// Search for an entity by a property
|
|
/// Search for an entity by a property
|
|
pub fn get_one_by<
|
|
pub fn get_one_by<
|
|
T: Entity<Column = C>,
|
|
T: Entity<Column = C>,
|
|
@@ -85,44 +149,56 @@ impl<'l> QueryInterface<'l> {
|
|
val: V,
|
|
val: V,
|
|
) -> Option<WithID<T>> {
|
|
) -> Option<WithID<T>> {
|
|
let table_name = <T as Entity>::table_name();
|
|
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()?;
|
|
|
|
-
|
|
|
|
- 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);
|
|
|
|
- Some(WithID::wrap(T::deserialize(&mut rd).ok()?, id))
|
|
|
|
- })
|
|
|
|
|
|
+ let column_name = <T as Entity>::name(c.clone());
|
|
|
|
+
|
|
|
|
+ self.cached_query_column(
|
|
|
|
+ "get_one_by",
|
|
|
|
+ std::any::TypeId::of::<T>(),
|
|
|
|
+ &c,
|
|
|
|
+ &|| {
|
|
|
|
+ self.db
|
|
|
|
+ .conn
|
|
|
|
+ .prepare(&format!(
|
|
|
|
+ "SELECT * FROM \"{}\" WHERE \"{}\" = ?",
|
|
|
|
+ table_name, column_name
|
|
|
|
+ ))
|
|
|
|
+ .expect("")
|
|
|
|
+ },
|
|
|
|
+ &mut |stmt| {
|
|
|
|
+ val.bind_to(stmt, 1).ok()?;
|
|
|
|
+
|
|
|
|
+ self.expect_one_result(stmt, &mut |stmt| {
|
|
|
|
+ let id: i64 = stmt.read(0).ok()?;
|
|
|
|
+ let mut rd = crate::model::load::RowDeserializer::from_row(stmt);
|
|
|
|
+ Some(WithID::wrap(T::deserialize(&mut rd).ok()?, id))
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+ )
|
|
}
|
|
}
|
|
|
|
|
|
/// Search for an entity by 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>> {
|
|
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 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()?;
|
|
|
|
|
|
|
|
- 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));
|
|
|
|
- })
|
|
|
|
|
|
+ self.cached_query(
|
|
|
|
+ "get_one_by_id",
|
|
|
|
+ std::any::TypeId::of::<T>(),
|
|
|
|
+ &|| {
|
|
|
|
+ self.db
|
|
|
|
+ .conn
|
|
|
|
+ .prepare(&format!("SELECT * FROM \"{}\" WHERE id = ?", table_name))
|
|
|
|
+ .expect("")
|
|
|
|
+ },
|
|
|
|
+ &mut |stmt| {
|
|
|
|
+ id.bind_to(stmt, 1).ok()?;
|
|
|
|
+
|
|
|
|
+ self.expect_one_result(stmt, &mut |stmt| {
|
|
|
|
+ let id: i64 = stmt.read(0).ok()?;
|
|
|
|
+ let mut rd = crate::model::load::RowDeserializer::from_row(stmt);
|
|
|
|
+ Some(WithID::wrap(T::deserialize(&mut rd).ok()?, id))
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+ )
|
|
}
|
|
}
|
|
|
|
|
|
/// Search for all entities matching a property
|
|
/// Search for all entities matching a property
|
|
@@ -136,8 +212,35 @@ impl<'l> QueryInterface<'l> {
|
|
val: V,
|
|
val: V,
|
|
) -> Option<Vec<WithID<T>>> {
|
|
) -> Option<Vec<WithID<T>>> {
|
|
let table_name = <T as Entity>::table_name();
|
|
let table_name = <T as Entity>::table_name();
|
|
- let column_name = <T as Entity>::name(c);
|
|
|
|
|
|
+ let column_name = <T as Entity>::name(c.clone());
|
|
|
|
+
|
|
|
|
+ self.cached_query_column(
|
|
|
|
+ "get_all_by",
|
|
|
|
+ std::any::TypeId::of::<T>(),
|
|
|
|
+ &c,
|
|
|
|
+ &|| {
|
|
|
|
+ self.db
|
|
|
|
+ .conn
|
|
|
|
+ .prepare(&format!(
|
|
|
|
+ "SELECT * FROM \"{}\" WHERE {} = ?",
|
|
|
|
+ table_name, column_name
|
|
|
|
+ ))
|
|
|
|
+ .expect("")
|
|
|
|
+ },
|
|
|
|
+ &mut |stmt| {
|
|
|
|
+ val.bind_to(stmt, 1).ok()?;
|
|
|
|
+
|
|
|
|
+ todo!()
|
|
|
|
+
|
|
|
|
+ /*self.expect_one_result(stmt, &mut |stmt| {
|
|
|
|
+ let id: i64 = stmt.read(0).ok()?;
|
|
|
|
+ let mut rd = crate::model::load::RowDeserializer::from_row(stmt);
|
|
|
|
+ Some(WithID::wrap(T::deserialize(&mut rd).ok()?, id))
|
|
|
|
+ })*/
|
|
|
|
+ },
|
|
|
|
+ )
|
|
|
|
|
|
|
|
+ /*
|
|
let mut prepared = self
|
|
let mut prepared = self
|
|
.db
|
|
.db
|
|
.conn
|
|
.conn
|
|
@@ -150,6 +253,7 @@ impl<'l> QueryInterface<'l> {
|
|
val.bind_to(&mut prepared, 1).ok()?;
|
|
val.bind_to(&mut prepared, 1).ok()?;
|
|
|
|
|
|
todo!();
|
|
todo!();
|
|
|
|
+ */
|
|
|
|
|
|
/*let rows = prepared
|
|
/*let rows = prepared
|
|
.query_map([&val], |row| {
|
|
.query_map([&val], |row| {
|
|
@@ -166,47 +270,30 @@ impl<'l> QueryInterface<'l> {
|
|
|
|
|
|
/// Add an entity to its table
|
|
/// Add an entity to its table
|
|
pub fn add<T: Entity + serde::Serialize>(&self, m: &T) -> Option<<T as Entity>::ID> {
|
|
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(|_| "?".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))*/
|
|
|
|
|
|
+ self.cached_query(
|
|
|
|
+ "get_all_by",
|
|
|
|
+ std::any::TypeId::of::<T>(),
|
|
|
|
+ &|| {
|
|
|
|
+ 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| {
|
|
|
|
+ crate::model::store::serialize_into(stmt, m).ok()?;
|
|
|
|
+
|
|
|
|
+ let rowid = self.expect_one_result(stmt, &mut |stmt| stmt.read::<i64>(0).ok())?;
|
|
|
|
+
|
|
|
|
+ Some(<T as Entity>::ID::from_raw_id(rowid))
|
|
|
|
+ },
|
|
|
|
+ )
|
|
}
|
|
}
|
|
}
|
|
}
|