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