|
@@ -5,11 +5,10 @@
|
|
//! - `qi.select().by(KVStore::Key, &key).result()`
|
|
//! - `qi.select().by(KVStore::Key, &key).result()`
|
|
|
|
|
|
use crate::{entity::EntityColumn, model::Modelable, Entity, Error};
|
|
use crate::{entity::EntityColumn, model::Modelable, Entity, Error};
|
|
-use std::{marker::PhantomData, collections::HashMap, hash::Hasher};
|
|
|
|
|
|
+use std::{marker::PhantomData, collections::HashMap, hash::{Hasher, Hash}};
|
|
|
|
|
|
use super::{WithID, QueryInterface};
|
|
use super::{WithID, QueryInterface};
|
|
|
|
|
|
-
|
|
|
|
#[derive(Debug,PartialEq,Eq,PartialOrd,Ord,Hash)]
|
|
#[derive(Debug,PartialEq,Eq,PartialOrd,Ord,Hash)]
|
|
pub enum QueryPart {
|
|
pub enum QueryPart {
|
|
Root,
|
|
Root,
|
|
@@ -46,7 +45,7 @@ impl DerivedQuery {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-trait StaticVersion {
|
|
|
|
|
|
+pub trait StaticVersion {
|
|
type Is: 'static;
|
|
type Is: 'static;
|
|
|
|
|
|
fn type_id() -> std::any::TypeId {
|
|
fn type_id() -> std::any::TypeId {
|
|
@@ -55,34 +54,48 @@ trait StaticVersion {
|
|
}
|
|
}
|
|
|
|
|
|
/// Any query component
|
|
/// Any query component
|
|
-trait QueryComponent: StaticVersion {
|
|
|
|
|
|
+pub trait QueryComponent: StaticVersion {
|
|
fn derive(&self) -> DerivedQuery {
|
|
fn derive(&self) -> DerivedQuery {
|
|
DerivedQuery::new()
|
|
DerivedQuery::new()
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ fn contribute<H: Hasher>(&self, hasher: &mut H);
|
|
|
|
+
|
|
/// returns the next index to use for binding
|
|
/// returns the next index to use for binding
|
|
fn bind(&self, stmt: &mut sqlite::Statement<'_>) -> Result<usize, Error>;
|
|
fn bind(&self, stmt: &mut sqlite::Statement<'_>) -> Result<usize, Error>;
|
|
}
|
|
}
|
|
|
|
|
|
-// helper functions
|
|
|
|
-
|
|
|
|
-fn get_stmt<'a, 'l>(qi: &'a QueryInterface<'l>) -> &'a mut sqlite::Statement<'l> {
|
|
|
|
- todo!()
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
/// Any query that can be completed/executed
|
|
/// Any query that can be completed/executed
|
|
-trait Resolvable<'r, 'q, T: Entity>: QueryComponent where 'q: 'r {
|
|
|
|
|
|
+pub trait Resolvable<'r, 'q, T: Entity>: QueryComponent where 'q: 'r {
|
|
fn qi(&self) -> &'r QueryInterface<'q>;
|
|
fn qi(&self) -> &'r QueryInterface<'q>;
|
|
|
|
|
|
- fn no_result(self) where Self: Sized { todo!() }
|
|
|
|
- fn result(self) -> Option<WithID<T>> where Self: Sized {
|
|
|
|
|
|
+ fn no_result(self) -> Option<()> where Self: Sized {
|
|
|
|
+ let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
|
|
|
+ self.contribute(&mut hasher);
|
|
|
|
+
|
|
self.qi().with_cache(
|
|
self.qi().with_cache(
|
|
- Self::type_id(),
|
|
|
|
|
|
+ hasher.finish(),
|
|
|| {
|
|
|| {
|
|
self.qi().db.conn.prepare(self.derive().assemble()).unwrap()
|
|
self.qi().db.conn.prepare(self.derive().assemble()).unwrap()
|
|
},
|
|
},
|
|
|stmt| {
|
|
|stmt| {
|
|
- self.bind(stmt);
|
|
|
|
|
|
+ self.bind(stmt).ok()?;
|
|
|
|
+
|
|
|
|
+ self.qi().expect_no_result(stmt)
|
|
|
|
+ }
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+ fn result(self) -> Option<WithID<T>> where Self: Sized {
|
|
|
|
+ let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
|
|
|
+ self.contribute(&mut hasher);
|
|
|
|
+ self.qi().with_cache(
|
|
|
|
+ hasher.finish(),
|
|
|
|
+ || {
|
|
|
|
+ let query = self.derive().assemble();
|
|
|
|
+ self.qi().db.conn.prepare(query).unwrap()
|
|
|
|
+ },
|
|
|
|
+ |stmt| {
|
|
|
|
+ self.bind(stmt).ok()?;
|
|
|
|
|
|
self.qi().expect_one_result(stmt, &mut |stmt| {
|
|
self.qi().expect_one_result(stmt, &mut |stmt| {
|
|
let id: i64 = stmt.read(0).ok()?;
|
|
let id: i64 = stmt.read(0).ok()?;
|
|
@@ -95,8 +108,10 @@ trait Resolvable<'r, 'q, T: Entity>: QueryComponent where 'q: 'r {
|
|
)
|
|
)
|
|
}
|
|
}
|
|
fn results(self) -> Vec<T> where Self: Sized {
|
|
fn results(self) -> Vec<T> where Self: Sized {
|
|
|
|
+ let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
|
|
|
+ self.contribute(&mut hasher);
|
|
self.qi().with_cache(
|
|
self.qi().with_cache(
|
|
- Self::type_id(),
|
|
|
|
|
|
+ hasher.finish(),
|
|
|| {
|
|
|| {
|
|
self.qi().db.conn.prepare(self.derive().assemble()).unwrap()
|
|
self.qi().db.conn.prepare(self.derive().assemble()).unwrap()
|
|
},
|
|
},
|
|
@@ -110,7 +125,7 @@ trait Resolvable<'r, 'q, T: Entity>: QueryComponent where 'q: 'r {
|
|
}
|
|
}
|
|
|
|
|
|
/// Any query that can have a WHERE clause attached to it
|
|
/// Any query that can have a WHERE clause attached to it
|
|
-trait Filterable<'r, 'q>: Resolvable<'r, 'q, Self::Table> where 'q: 'r{
|
|
|
|
|
|
+pub trait Filterable<'r, 'q>: Resolvable<'r, 'q, Self::Table> where 'q: 'r{
|
|
type Table: Entity;
|
|
type Table: Entity;
|
|
|
|
|
|
fn by<C: EntityColumn<Entity = Self::Table>, G: Modelable + ?Sized>(self, col: C, given: &'r G) -> Filter<'r, 'q, Self, C, G> where Self: Sized {
|
|
fn by<C: EntityColumn<Entity = Self::Table>, G: Modelable + ?Sized>(self, col: C, given: &'r G) -> Filter<'r, 'q, Self, C, G> where Self: Sized {
|
|
@@ -138,6 +153,11 @@ impl<'r, 'q, T: Entity> QueryComponent for Select<'r, 'q, T> {
|
|
DerivedQuery::new().add(QueryPart::Root, format!("SELECT * FROM {}", T::table_name()))
|
|
DerivedQuery::new().add(QueryPart::Root, format!("SELECT * FROM {}", T::table_name()))
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ fn contribute<H: Hasher>(&self, hasher: &mut H) {
|
|
|
|
+ "select".hash(hasher);
|
|
|
|
+ std::any::TypeId::of::<T>().hash(hasher);
|
|
|
|
+ }
|
|
|
|
+
|
|
// next binding point is the first
|
|
// next binding point is the first
|
|
fn bind(&self, _stmt: &mut sqlite::Statement<'_>) -> Result<usize, Error> { Ok(1) }
|
|
fn bind(&self, _stmt: &mut sqlite::Statement<'_>) -> Result<usize, Error> { Ok(1) }
|
|
}
|
|
}
|
|
@@ -150,8 +170,45 @@ impl<'r, 'q, T: Entity> Resolvable<'r, 'q, T> for Select<'r, 'q, T> {
|
|
fn qi(&self) -> &'r QueryInterface<'q> { self.qi }
|
|
fn qi(&self) -> &'r QueryInterface<'q> { self.qi }
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+pub struct Delete<'r, 'q, T: Entity> {
|
|
|
|
+ qi: &'r QueryInterface<'q>,
|
|
|
|
+ _ghost: PhantomData<T>,
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl<'r, 'q, T: Entity> Delete<'r, 'q, T> {
|
|
|
|
+ pub fn new(qi: &'r QueryInterface<'q>) -> Self {
|
|
|
|
+ Self { qi, _ghost: std::marker::PhantomData }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl <'r, 'q, T: Entity> StaticVersion for Delete<'r, 'q, T> {
|
|
|
|
+ type Is = Select<'static, 'static, T>;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl<'r, 'q, T: Entity> QueryComponent for Delete<'r, 'q, T> {
|
|
|
|
+ fn derive(&self) -> DerivedQuery {
|
|
|
|
+ DerivedQuery::new().add(QueryPart::Root, format!("DELETE FROM {}", T::table_name()))
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fn contribute<H: Hasher>(&self, hasher: &mut H) {
|
|
|
|
+ "delete".hash(hasher);
|
|
|
|
+ std::any::TypeId::of::<T>().hash(hasher);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // next binding point is the first
|
|
|
|
+ fn bind(&self, _stmt: &mut sqlite::Statement<'_>) -> Result<usize, Error> { Ok(1) }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl<'r, 'q, T: Entity> Filterable<'r, 'q> for Delete<'r, 'q, T> {
|
|
|
|
+ type Table = T;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl<'r, 'q, T: Entity> Resolvable<'r, 'q, T> for Delete<'r, 'q, T> {
|
|
|
|
+ fn qi(&self) -> &'r QueryInterface<'q> { self.qi }
|
|
|
|
+}
|
|
|
|
+
|
|
/// A concrete WHERE clause
|
|
/// A concrete WHERE clause
|
|
-struct Filter<'r, 'q, F: Filterable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized> where 'q: 'r {
|
|
|
|
|
|
+pub struct Filter<'r, 'q, F: Filterable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized> where 'q: 'r {
|
|
wrap: F,
|
|
wrap: F,
|
|
col: C,
|
|
col: C,
|
|
given: &'r G,
|
|
given: &'r G,
|
|
@@ -167,6 +224,12 @@ impl<'r, 'q, F: Filterable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized> Quer
|
|
self.wrap.derive().add(QueryPart::Where, format!("{} = ?", self.col.name()))
|
|
self.wrap.derive().add(QueryPart::Where, format!("{} = ?", self.col.name()))
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ fn contribute<H: Hasher>(&self, hasher: &mut H) {
|
|
|
|
+ self.wrap.contribute(hasher);
|
|
|
|
+ std::any::TypeId::of::<Self::Is>().hash(hasher);
|
|
|
|
+ std::any::TypeId::of::<C>().hash(hasher);
|
|
|
|
+ }
|
|
|
|
+
|
|
fn bind(&self, stmt: &mut sqlite::Statement<'_>) -> Result<usize, crate::Error> {
|
|
fn bind(&self, stmt: &mut sqlite::Statement<'_>) -> Result<usize, crate::Error> {
|
|
let next_index = self.wrap.bind(stmt)?;
|
|
let next_index = self.wrap.bind(stmt)?;
|
|
|
|
|
|
@@ -184,26 +247,3 @@ impl<'r, 'q, F: Filterable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized> Filt
|
|
type Table = C::Entity;
|
|
type Table = C::Entity;
|
|
}
|
|
}
|
|
|
|
|
|
-#[cfg(test)]
|
|
|
|
-mod test_build {
|
|
|
|
- use microrm_macros::Entity;
|
|
|
|
- use serde::{Deserialize, Serialize};
|
|
|
|
-
|
|
|
|
- use crate::QueryInterface;
|
|
|
|
-
|
|
|
|
- #[derive(Entity, Serialize, Deserialize)]
|
|
|
|
- #[microrm_internal]
|
|
|
|
- pub struct KVStore {
|
|
|
|
- key: String,
|
|
|
|
- value: String,
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- #[test]
|
|
|
|
- fn simple_get() {
|
|
|
|
- use super::*;
|
|
|
|
- let db = crate::DB::new_in_memory(crate::Schema::new().entity::<KVStore>()).unwrap();
|
|
|
|
- let qi = db.query_interface();
|
|
|
|
-
|
|
|
|
- assert!(qi.get().by(KVStore::Key, "abc").result().is_none());
|
|
|
|
- }
|
|
|
|
-}
|
|
|