|
@@ -5,218 +5,11 @@ use crate::{
|
|
|
};
|
|
|
use crate::{DBError, DBResult};
|
|
|
|
|
|
+mod build;
|
|
|
mod entity;
|
|
|
pub(crate) mod meta;
|
|
|
mod tests;
|
|
|
|
|
|
-#[derive(Debug)]
|
|
|
-struct ColumnInfo {
|
|
|
- name: &'static str,
|
|
|
- ty: String,
|
|
|
- fkey: Option<String>,
|
|
|
- unique: bool,
|
|
|
-}
|
|
|
-
|
|
|
-#[derive(Debug)]
|
|
|
-struct TableInfo {
|
|
|
- table_name: String,
|
|
|
- columns: Vec<ColumnInfo>,
|
|
|
- dependencies: Vec<String>,
|
|
|
-}
|
|
|
-
|
|
|
-impl TableInfo {
|
|
|
- fn new(name: String) -> Self {
|
|
|
- TableInfo {
|
|
|
- table_name: name,
|
|
|
- columns: vec![],
|
|
|
- dependencies: vec![],
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- fn build_creation_query(&self) -> String {
|
|
|
- let columns = self.columns.iter().map(|col| {
|
|
|
- format!(
|
|
|
- ", `{}` {}{}",
|
|
|
- col.name,
|
|
|
- col.ty,
|
|
|
- if col.unique { "unique" } else { "" }
|
|
|
- )
|
|
|
- });
|
|
|
- let fkeys = self.columns.iter().filter_map(|col| {
|
|
|
- Some(format!(
|
|
|
- ", foreign key(`{}`) references {}",
|
|
|
- col.name,
|
|
|
- col.fkey.as_ref()?
|
|
|
- ))
|
|
|
- });
|
|
|
-
|
|
|
- format!(
|
|
|
- "create table {} (`id` integer primary key{}{});",
|
|
|
- self.table_name,
|
|
|
- columns.collect::<String>(),
|
|
|
- fkeys.collect::<String>()
|
|
|
- )
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-pub struct DatabaseSchema {
|
|
|
- signature: u64,
|
|
|
- queries: Vec<String>,
|
|
|
-}
|
|
|
-
|
|
|
-impl DatabaseSchema {
|
|
|
- const SCHEMA_SIGNATURE_KEY: &'static str = "schema_signature";
|
|
|
- pub fn check(&self, db: DBConnection) -> bool {
|
|
|
- // attempt to use connection as a MetadataDB database
|
|
|
- let metadb = meta::MetadataDB::build(db);
|
|
|
-
|
|
|
- // check to see if the signature exists and matches
|
|
|
- metadb
|
|
|
- .kv_metastore
|
|
|
- .lookup_unique(&Self::SCHEMA_SIGNATURE_KEY.to_string())
|
|
|
- .ok()
|
|
|
- .flatten()
|
|
|
- .map(|kv| kv.value.parse::<u64>().unwrap_or(0) == self.signature)
|
|
|
- .unwrap_or(false)
|
|
|
- }
|
|
|
-
|
|
|
- pub fn create(&self, db: DBConnection) {
|
|
|
- for query in self.queries.iter() {
|
|
|
- db.execute_raw(query).expect("Couldn't run creation query!");
|
|
|
- }
|
|
|
-
|
|
|
- // attempt to use connection as a MetadataDB database
|
|
|
- let metadb = meta::MetadataDB::build(db.clone());
|
|
|
-
|
|
|
- for query in collect_from_database::<meta::MetadataDB>().queries.iter() {
|
|
|
- db.execute_raw(query)
|
|
|
- .expect("Couldn't run MetadataDB creation query!");
|
|
|
- }
|
|
|
-
|
|
|
- // store signature
|
|
|
- metadb
|
|
|
- .kv_metastore
|
|
|
- .insert(&meta::KV {
|
|
|
- key: Self::SCHEMA_SIGNATURE_KEY.into(),
|
|
|
- value: format!("{}", self.signature),
|
|
|
- })
|
|
|
- .expect("couldn't set schema signature");
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-pub(crate) fn collect_from_database<DB: Database>() -> DatabaseSchema {
|
|
|
- struct IV(entity::EntityStateContainer);
|
|
|
-
|
|
|
- impl DatabaseItemVisitor for IV {
|
|
|
- fn visit<DI: DatabaseItem>(&mut self)
|
|
|
- where
|
|
|
- Self: Sized,
|
|
|
- {
|
|
|
- DI::accept_entity_visitor(&mut self.0.make_context(DI::item_key()));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- let mut iv = IV(entity::EntityStateContainer::default());
|
|
|
-
|
|
|
- DB::accept_item_visitor(&mut iv);
|
|
|
-
|
|
|
- // now to turn all that into a set of tables
|
|
|
- let mut tables = std::collections::HashMap::new();
|
|
|
-
|
|
|
- for state in iv.0.iter_states() {
|
|
|
- let table_name = format!("{}_{}", state.context, state.name);
|
|
|
- // we may end up visiting duplicate entities; skip them if so
|
|
|
- if tables.contains_key(&table_name) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- let mut table = TableInfo::new(table_name.clone());
|
|
|
- for part in state.parts.iter() {
|
|
|
- match part.ty {
|
|
|
- entity::PartType::Datum(dtype) => {
|
|
|
- table.columns.push(ColumnInfo {
|
|
|
- name: part.name,
|
|
|
- ty: dtype.into(),
|
|
|
- fkey: None,
|
|
|
- unique: false, // XXX
|
|
|
- })
|
|
|
- }
|
|
|
- entity::PartType::Assoc(assoc_name) => {
|
|
|
- let assoc_table_name = format!(
|
|
|
- "{}_{}_assoc_{}_{}",
|
|
|
- state.context, state.name, part.name, assoc_name
|
|
|
- );
|
|
|
- let mut assoc_table = TableInfo::new(assoc_table_name.clone());
|
|
|
- assoc_table.dependencies.push(table_name.clone());
|
|
|
- assoc_table
|
|
|
- .dependencies
|
|
|
- .push(format!("{}_{}", state.context, assoc_name));
|
|
|
-
|
|
|
- assoc_table.columns.push(ColumnInfo {
|
|
|
- name: "base".into(),
|
|
|
- ty: "int".into(),
|
|
|
- fkey: Some(format!("{}(`id`)", table_name)),
|
|
|
- unique: false,
|
|
|
- });
|
|
|
-
|
|
|
- assoc_table.columns.push(ColumnInfo {
|
|
|
- name: "target".into(),
|
|
|
- ty: "int".into(),
|
|
|
- fkey: Some(format!("{}_{}(`id`)", state.context, assoc_name)),
|
|
|
- unique: false,
|
|
|
- });
|
|
|
-
|
|
|
- tables.insert(assoc_table_name, assoc_table);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- tables.insert(table_name, table);
|
|
|
- }
|
|
|
-
|
|
|
- let mut tsort: topological_sort::TopologicalSort<&str> =
|
|
|
- topological_sort::TopologicalSort::new();
|
|
|
- for table in tables.values() {
|
|
|
- tsort.insert(table.table_name.as_str());
|
|
|
-
|
|
|
- for dep in table.dependencies.iter() {
|
|
|
- tsort.add_dependency(dep.as_str(), table.table_name.as_str());
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // this must be a stable hash function, so we very explicitly want to use a SipHasher with
|
|
|
- // known parameters
|
|
|
- #[allow(deprecated)]
|
|
|
- let mut signature_hasher = std::hash::SipHasher::new();
|
|
|
- use std::hash::{Hash, Hasher};
|
|
|
-
|
|
|
- let mut queries = vec![];
|
|
|
-
|
|
|
- loop {
|
|
|
- let mut table_list = tsort.pop_all();
|
|
|
- if table_list.len() == 0 {
|
|
|
- break;
|
|
|
- }
|
|
|
- // bring into stable ordering
|
|
|
- table_list.sort();
|
|
|
-
|
|
|
- for table_name in table_list.into_iter() {
|
|
|
- let table = tables.get(table_name).unwrap();
|
|
|
- let create_sql = table.build_creation_query();
|
|
|
-
|
|
|
- table_name.hash(&mut signature_hasher);
|
|
|
- create_sql.hash(&mut signature_hasher);
|
|
|
- queries.push(create_sql);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // TODO: generate index schemas here
|
|
|
-
|
|
|
- DatabaseSchema {
|
|
|
- signature: signature_hasher.finish(),
|
|
|
- queries,
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
// ----------------------------------------------------------------------
|
|
|
// Specification types
|
|
|
// ----------------------------------------------------------------------
|
|
@@ -244,6 +37,11 @@ impl<T: Entity> IDMap<T> {
|
|
|
self.ctx
|
|
|
}
|
|
|
|
|
|
+ pub fn by_id(&self, _id: T::ID) -> DBResult<Option<T>> {
|
|
|
+ // query::select_by
|
|
|
+ todo!()
|
|
|
+ }
|
|
|
+
|
|
|
pub fn lookup_unique(
|
|
|
&self,
|
|
|
uniques: &<<T as Entity>::Uniques as EntityPartList>::DatumList,
|
|
@@ -267,13 +65,21 @@ pub struct Index<T: Entity, Key: EntityDatum> {
|
|
|
_ghost: std::marker::PhantomData<(T, Key)>,
|
|
|
}
|
|
|
|
|
|
+pub(crate) struct AssocData {
|
|
|
+ pub(crate) conn: DBConnection,
|
|
|
+ pub(crate) ctx: &'static str,
|
|
|
+ pub(crate) base_rowid: Option<i64>,
|
|
|
+}
|
|
|
+
|
|
|
pub struct AssocSet<T: Entity> {
|
|
|
+ data: Option<AssocData>,
|
|
|
_ghost: std::marker::PhantomData<T>,
|
|
|
}
|
|
|
|
|
|
impl<T: Entity> AssocSet<T> {
|
|
|
pub fn empty() -> Self {
|
|
|
Self {
|
|
|
+ data: None,
|
|
|
_ghost: Default::default(),
|
|
|
}
|
|
|
}
|
|
@@ -293,14 +99,26 @@ impl<T: Entity> EntityDatum for AssocSet<T> {
|
|
|
}
|
|
|
|
|
|
fn build_from<'a>(
|
|
|
- _conn: &DBConnection,
|
|
|
- _stmt: &mut sqlite::Statement<'a>,
|
|
|
- _index: usize,
|
|
|
+ conn: &DBConnection,
|
|
|
+ ctx: &'static str,
|
|
|
+ stmt: &mut sqlite::Statement<'a>,
|
|
|
+ index: usize,
|
|
|
) -> DBResult<(Self, usize)>
|
|
|
where
|
|
|
Self: Sized,
|
|
|
{
|
|
|
- todo!()
|
|
|
+ // assuming that the stmt has the rowid as index 0
|
|
|
+ Ok((
|
|
|
+ Self {
|
|
|
+ data: Some(AssocData {
|
|
|
+ conn: conn.clone(),
|
|
|
+ ctx,
|
|
|
+ base_rowid: stmt.read(0)?,
|
|
|
+ }),
|
|
|
+ _ghost: Default::default(),
|
|
|
+ },
|
|
|
+ index,
|
|
|
+ ))
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -343,7 +161,7 @@ pub trait Database {
|
|
|
Self: Sized,
|
|
|
{
|
|
|
let conn = Connection::open(uri)?;
|
|
|
- let schema = collect_from_database::<Self>();
|
|
|
+ let schema = build::collect_from_database::<Self>();
|
|
|
if !schema.check(conn.clone()) {
|
|
|
schema.create(conn.clone())
|
|
|
}
|