microrm 0.6.3

Lightweight ORM using sqlite as a backend
Documentation
use crate::{schema, DBResult, Transaction};

pub fn check_schema<S: schema::Schema>(txn: &mut Transaction) -> DBResult<()> {
    let raw_schema = crate::db::get_raw_schema(txn.lease())?;
    let generated = schema::build::generate_from_schema::<S>();
    let metagenerated = schema::build::generate_single_entity_table::<schema::meta::MicrormMeta>();

    let mut tablecount = 0;
    let mut indexcount = 0;

    for rs in raw_schema {
        if rs.type_ == "table" {
            // confirm generated SQL is the same
            if rs.tbl_name == "microrm_meta" {
                if metagenerated != rs.sql {
                    log::error!("Metadata schema inconsistent between DB and schema");
                    return Err(crate::Error::ConsistencyError(
                        "Table SQL mismatch for metadata".to_string(),
                    ));
                }
            } else if Some(&rs.sql) != generated.table_queries().get(&rs.tbl_name) {
                log::error!("Mismatch in SQL for {}", rs.tbl_name);
                log::trace!(
                    "Should be: {:?}",
                    generated.table_queries().get(&rs.tbl_name)
                );
                log::trace!("Found in DB: {:?}", rs.sql);
                return Err(crate::Error::ConsistencyError(format!(
                    "Table SQL mismatch for {}",
                    rs.tbl_name
                )));
            } else {
                tablecount += 1;
            }
        } else if rs.type_ == "index" {
            if rs.name.starts_with("sqlite_autoindex") {
                continue;
            }
            log::trace!("processing index {} on {}", rs.name, rs.tbl_name);

            let Some(generated_sql) = generated.index_queries().get(&rs.name) else {
                log::trace!("index queries: {:?}", generated.index_queries());
                return Err(crate::Error::ConsistencyError(format!(
                    "Index SQL not present in generated queries for index {}",
                    rs.name
                )));
            };

            if generated_sql == &rs.sql {
                indexcount += 1;
            } else {
                log::debug!("generated sql: {generated_sql}");
                log::debug!("present sql:   {}", rs.sql);
                return Err(crate::Error::ConsistencyError(format!(
                    "Index SQL mismatch for index {}",
                    rs.name
                )));
            }
        } else {
            log::warn!(
                "unknown sqlite schema element type {} for {}",
                rs.type_,
                rs.name
            );
        }
    }

    if tablecount != generated.table_queries().len() {
        log::error!(
            "Incorrect number of tables in DB schema: should be {}, is {}",
            generated.table_queries().len(),
            tablecount
        );
        Err(crate::Error::ConsistencyError(
            "Table count mismatch".to_string(),
        ))
    } else if indexcount != generated.index_queries().len() {
        log::error!(
            "Incorrect number of indicies in DB schema: should be {}, is {}",
            generated.index_queries().len(),
            indexcount
        );
        Err(crate::Error::ConsistencyError(
            "Index count mismatch".to_string(),
        ))
    } else {
        cfg_if::cfg_if! {
            if #[cfg(feature = "track_typesig")] {
                use crate::schema::{self, meta, DatabaseItem, typesig};
                use crate::{query::Queryable, Error};
                use std::collections::HashSet;

                // check type signature details
                let meta = meta::MetadataDB::build(schema::BuildSeal::new());

                let Some(stored_json) = meta
                    .metastore
                    .keyed(typesig::TYPE_SIGNATURES_KEY)
                    .get(txn)?
                else {
                    log::warn!("No schema type signatures stored in database.");
                    return Ok(());
                };
                let stored_typesigs: typesig::BorrowedSignatureMap =
                    serde_json::from_str(stored_json.value.as_str()).map_err(|_| {
                        Error::InternalError("could not deserialize stored type signatures")
                    })?;

                let csigs = typesig::collect_type_info::<S>();

                let mut equal = HashSet::<&str>::new();

                let mut mismatch = false;

                for (name, tsig) in stored_typesigs.into_iter() {
                    let Some(csig) = csigs.get(name) else {
                        continue;
                    };

                    if &tsig != csig {
                        log::debug!("Different type signatures:");
                        log::debug!("stored: {tsig:#?}");
                        log::debug!("current: {csig:#?}");
                        mismatch = true;
                    } else {
                        equal.insert(name);
                    }
                }

                if mismatch {
                    Err(Error::IncompatibleSchema)
                } else {
                    Ok(())
                }
            } else {
                Ok(())
            }
        }
    }
}