use std::fmt;
use std::string::String;
use std::vec::Vec;
use miden_protocol::crypto::hash::blake::{Blake3_256, Blake3Digest};
use rusqlite::Connection;
use super::errors::SqliteStoreError;
const SCHEMA_HASH_DOMAIN: &[u8] = b"miden-client-sqlite-schema-v1";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct SchemaHash(Blake3Digest<32>);
impl SchemaHash {
pub(crate) fn of(conn: &Connection) -> Result<Self, SqliteStoreError> {
let mut stmt = conn.prepare(
"SELECT type, name, tbl_name, sql FROM sqlite_schema \
WHERE sql IS NOT NULL AND name NOT GLOB 'sqlite_*' \
ORDER BY type, name, tbl_name",
)?;
let entries = stmt
.query_map([], |row| {
Ok((
row.get::<_, String>(0)?,
row.get::<_, String>(1)?,
row.get::<_, String>(2)?,
normalize_sql(&row.get::<_, String>(3)?),
))
})?
.collect::<rusqlite::Result<Vec<_>>>()?;
let mut buf = Vec::new();
push_field(&mut buf, SCHEMA_HASH_DOMAIN);
for (object_type, name, table_name, sql) in entries {
push_field(&mut buf, object_type.as_bytes());
push_field(&mut buf, name.as_bytes());
push_field(&mut buf, table_name.as_bytes());
push_field(&mut buf, sql.as_bytes());
}
Ok(Self(Blake3_256::hash(&buf)))
}
}
impl fmt::Display for SchemaHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&String::from(self.0))
}
}
fn push_field(buf: &mut Vec<u8>, field: &[u8]) {
buf.extend_from_slice(&(field.len() as u64).to_le_bytes());
buf.extend_from_slice(field);
}
fn normalize_sql(sql: &str) -> String {
sql.trim_end()
.trim_end_matches(';')
.split_whitespace()
.collect::<Vec<_>>()
.join(" ")
}
#[cfg(test)]
mod tests {
use rusqlite::Connection;
use super::SchemaHash;
#[test]
fn schema_hash_ignores_object_creation_order() {
let left = Connection::open_in_memory().unwrap();
left.execute_batch(
"CREATE TABLE a (id INTEGER PRIMARY KEY);
CREATE TABLE b (id INTEGER PRIMARY KEY);",
)
.unwrap();
let right = Connection::open_in_memory().unwrap();
right
.execute_batch(
"CREATE TABLE b (id INTEGER PRIMARY KEY);
CREATE TABLE a (id INTEGER PRIMARY KEY);",
)
.unwrap();
assert_eq!(SchemaHash::of(&left).unwrap(), SchemaHash::of(&right).unwrap());
}
}