#![allow(dead_code, unused_macros)]
#![allow(
clippy::missing_panics_doc,
clippy::must_use_candidate,
clippy::new_without_default,
clippy::unwrap_used
)]
use std::fmt::Write as _;
use digest::Digest;
use insta::assert_snapshot;
use rusqlite::types::FromSql;
use rusqlite::{Connection, Result};
#[must_use]
pub fn hash<T: Digest>(input: &[u8]) -> Vec<u8> {
let mut hasher = T::new();
hasher.update(input);
hasher.finalize().to_vec()
}
#[must_use]
pub fn hash_hex<T: Digest>(input: &[u8]) -> String {
hash::<T>(input)
.into_iter()
.fold(String::new(), |mut output, b| {
let _ = write!(output, "{b:02X}");
output
})
}
fn hasher() {
#[cfg(feature = "md5")]
assert_snapshot!(hash_hex::<md5::Md5>("test".as_bytes()), @"098F6BCD4621D373CADE4E832627B4F6");
#[cfg(feature = "sha1")]
assert_snapshot!(hash_hex::<sha1::Sha1>("test".as_bytes()), @"A94A8FE5CCB19BA61C4C0873D391E987982FBBD3");
#[cfg(feature = "sha224")]
assert_snapshot!(hash_hex::<sha2::Sha224>("test".as_bytes()), @"90A3ED9E32B2AAF4C61C410EB925426119E1A9DC53D4286ADE99A809");
#[cfg(feature = "sha256")]
assert_snapshot!(hash_hex::<sha2::Sha256>("test".as_bytes()), @"9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08");
#[cfg(feature = "sha384")]
assert_snapshot!(hash_hex::<sha2::Sha384>("test".as_bytes()), @"768412320F7B0AA5812FCE428DC4706B3CAE50E02A64CAA16A782249BFE8EFC4B7EF1CCB126255D196047DFEDF17A0A9");
#[cfg(feature = "sha512")]
assert_snapshot!(hash_hex::<sha2::Sha512>("test".as_bytes()), @"EE26B0DD4AF7E749AA1A8EE3C10AE9923F618980772E473F8819A5D4940E0DB27AC185F8A0E1D5F84F88BC887FD67B143732C304CC5FA9AD8E6F57F50028A8FF");
#[cfg(feature = "blake3")]
assert_snapshot!(hash_hex::<blake3::Hasher>("test".as_bytes()), @"BA80A53F981C4D0D6A2797BEEA0D8B8A7A0B1E8B6A27E4F7A0E3C6C7E6F7A0E3C6C7E6F7A0E3C6C7E6F7A0E3C6C7E6F");
#[cfg(feature = "fnv")]
assert_snapshot!(hash_hex::<noncrypto_digests::Fnv>("test".as_bytes()), @"EE26B0DD4AF7E749AA1A8EE3C10AE9923F618980772E473F8819A5D4940E0DB27AC185F8A0E1D5F84F88BC887FD67B143732C304CC5FA9AD8E6F57F50028A8FF");
#[cfg(feature = "xxhash")]
{
assert_snapshot!(hash_hex::<noncrypto_digests::Xxh32>("test".as_bytes()), @"EE26B0DD4AF7E749AA1A8EE3C10AE9923F618980772E473F8819A5D4940E0DB27AC185F8A0E1D5F84F88BC887FD67B143732C304CC5FA9AD8E6F57F50028A8FF");
assert_snapshot!(hash_hex::<noncrypto_digests::Xxh64>("test".as_bytes()), @"EE26B0DD4AF7E749AA1A8EE3C10AE9923F618980772E473F8819A5D4940E0DB27AC185F8A0E1D5F84F88BC887FD67B143732C304CC5FA9AD8E6F57F50028A8FF");
assert_snapshot!(hash_hex::<noncrypto_digests::Xxh3_64>("test".as_bytes()), @"EE26B0DD4AF7E749AA1A8EE3C10AE9923F618980772E473F8819A5D4940E0DB27AC185F8A0E1D5F84F88BC887FD67B143732C304CC5FA9AD8E6F57F50028A8FF");
assert_snapshot!(hash_hex::<noncrypto_digests::Xxh3_128>("test".as_bytes()), @"EE26B0DD4AF7E749AA1A8EE3C10AE9923F618980772E473F8819A5D4940E0DB27AC185F8A0E1D5F84F88BC887FD67B143732C304CC5FA9AD8E6F57F50028A8FF");
}
}
macro_rules! hash_macros {
( $( $feat:literal $name:ident $typ:ty ),* $(,)? ) => {
$(
#[cfg(feature = $feat)]
macro_rules! $name {
( $actual:expr, NULL ) => {{
let actual: rusqlite::Result<Option<Vec<u8>>> = $actual;
assert_eq!(actual, Ok(None), "asserting NULL result");
}};
( $actual:expr, EMPTY ) => {{
let actual: rusqlite::Result<Option<String>> = $actual;
assert_eq!(actual, Ok(Some(String::from(""))), "asserting EMPTY result");
}};
( $actual:expr, ERROR ) => {{
let actual: rusqlite::Result<Vec<u8>, rusqlite::Error> = $actual;
assert!(actual.is_err(), "asserting error result");
}};
( $actual:expr, NO_ROWS ) => {{
let actual: rusqlite::Result<Vec<Vec<u8>>> = $actual;
assert!(actual.unwrap().is_empty(), "asserting NO_ROWS result");
}};
( $actual:expr, blob[ $vec:expr ] ) => {{
let actual: rusqlite::Result<Vec<Vec<u8>>> = $actual;
assert_eq!(actual.unwrap(), $vec.iter().map(|v| $crate::utils::hash::<$typ>(v.as_bytes())).collect::<Vec<Vec<u8>>>())
}};
( $actual:expr, bytes_as_blob[ $vec:expr ] ) => {{
let actual: rusqlite::Result<Vec<Vec<u8>>> = $actual;
assert_eq!(actual.unwrap(), $vec.iter().map(|v| $crate::utils::hash::<$typ>(v)).collect::<Vec<Vec<u8>>>())
}};
( $actual:expr, bytes_as_hex[ $vec:expr ] ) => {{
let actual: rusqlite::Result<Vec<String>> = $actual;
assert_eq!(actual.unwrap(), $vec.iter().map(|v| $crate::utils::hash_hex::<$typ>(v)).collect::<Vec<Vec<u8>>>())
}};
( $actual:expr, hex[ $vec:expr ] ) => {{
let actual: rusqlite::Result<Vec<String>> = $actual;
assert_eq!(actual.unwrap(), $vec.iter().map(|v| $crate::utils::hash_hex::<$typ>(v.as_bytes())).collect::<Vec<String>>())
}};
( $actual:expr, blob($expected:expr) ) => {{
let actual: rusqlite::Result<Vec<u8>> = $actual;
assert_eq!(actual.unwrap(), $crate::utils::hash::<$typ>($expected.as_bytes()))
}};
( $actual:expr, bytes_as_blob($expected:expr) ) => {{
let actual: rusqlite::Result<Vec<u8>> = $actual;
assert_eq!(actual.unwrap(), $crate::utils::hash::<$typ>($expected))
}};
( $actual:expr, bytes_as_hex($expected:expr) ) => {{
let actual: rusqlite::Result<String> = $actual;
assert_eq!(actual.unwrap(), $crate::utils::hash_hex::<$typ>($expected))
}};
( $actual:expr, hex($expected:expr) ) => {{
let actual: rusqlite::Result<String> = $actual;
assert_eq!(actual.unwrap(), $crate::utils::hash_hex::<$typ>($expected.as_bytes()))
}};
}
#[cfg(not(feature = $feat))]
macro_rules! $name {
($actual:expr, $exp:expr ) => {};
}
)*
};
}
hash_macros!(
"md5" md5 md5::Md5,
"sha1" sha1 sha1::Sha1,
"sha224" sha224 sha2::Sha224,
"sha256" sha256 sha2::Sha256,
"sha384" sha384 sha2::Sha384,
"sha512" sha512 sha2::Sha512,
"blake3" blake3 blake3::Hasher,
"fnv" fnv1a noncrypto_digests::Fnv,
"xxhash" xxh32 noncrypto_digests::Xxh32,
"xxhash" xxh64 noncrypto_digests::Xxh64,
"xxhash" xxh3_64 noncrypto_digests::Xxh3_64,
"xxhash" xxh3_128 noncrypto_digests::Xxh3_128,
);
macro_rules! test_all {
( $conn:ident.$func:ident(*$suffix:tt), $($any:tt)* ) => {{
let suffix = stringify!($suffix);
test_all!( $conn.$func(suffix), $($any)* )
}};
( $conn:ident.$func:ident($suffix:expr), $($any:tt)* ) => {{
let suffix = $suffix;
md5!( $conn.$func(&format!("md5{suffix}")), $($any)* );
sha1!( $conn.$func(&format!("sha1{suffix}")), $($any)* );
sha224!( $conn.$func(&format!("sha224{suffix}")), $($any)* );
sha256!( $conn.$func(&format!("sha256{suffix}")), $($any)* );
sha384!( $conn.$func(&format!("sha384{suffix}")), $($any)* );
sha512!( $conn.$func(&format!("sha512{suffix}")), $($any)* );
blake3!( $conn.$func(&format!("blake3{suffix}")), $($any)* );
fnv1a!( $conn.$func(&format!("fnv1a{suffix}")), $($any)* );
xxh32!( $conn.$func(&format!("xxh32{suffix}")), $($any)* );
xxh64!( $conn.$func(&format!("xxh64{suffix}")), $($any)* );
xxh3_64!( $conn.$func(&format!("xxh3_64{suffix}")), $($any)* );
xxh3_128!( $conn.$func(&format!("xxh3_128{suffix}")), $($any)* );
}};
}
pub struct Conn(Connection);
impl Conn {
pub fn new() -> Self {
let db = Connection::open_in_memory().unwrap();
sqlite_hashes::register_hash_functions(&db).unwrap();
db.execute_batch(
"
CREATE TABLE tbl(id INTEGER PRIMARY KEY, v_text TEXT, v_blob BLOB, v_null_text TEXT, v_null_blob BLOB);
INSERT INTO tbl VALUES
(1, 'bbb', cast('bbb' as BLOB), cast(NULL as TEXT), cast(NULL as BLOB)),
(2, 'ccc', cast('ccc' as BLOB), cast(NULL as TEXT), cast(NULL as BLOB)),
(3, 'aaa', cast('aaa' as BLOB), cast(NULL as TEXT), cast(NULL as BLOB));
",
)
.unwrap();
Self(db)
}
pub fn sql<T: FromSql>(&self, query: &str) -> Result<T> {
self.0.query_row_and_then(query, [], |r| r.get(0))
}
pub fn list<T: FromSql>(&self, query: &str) -> Result<Vec<T>> {
let mut stmt = self.0.prepare(query).unwrap();
stmt.query_map([], |row| row.get::<_, T>(0))
.unwrap()
.collect::<Result<Vec<T>>>()
}
pub fn select<T: FromSql>(&self, func: &str) -> Result<T> {
self.sql(&format!("SELECT {func}"))
}
pub fn growing_text_seq<T: FromSql>(&self, func: &str) -> Result<Vec<T>> {
let sql = format!("SELECT {func}(v_text) OVER (ORDER BY v_text) FROM tbl");
self.list(&sql)
}
pub fn legacy_text_aggregate<T: FromSql>(&self, hash: &str) -> Result<T> {
let sql = format!("SELECT {hash}(v_text) FROM (SELECT v_text FROM tbl ORDER BY v_text)");
self.sql(&sql)
}
pub fn legacy_blob_aggregate<T: FromSql>(&self, hash: &str) -> Result<T> {
let sql = format!("SELECT {hash}(v_blob) FROM (SELECT v_blob FROM tbl ORDER BY v_text)");
self.sql(&sql)
}
pub fn legacy_null_text_aggregate<T: FromSql>(&self, hash: &str) -> Result<T> {
let sql = format!(
"SELECT {hash}(v_null_text) FROM (SELECT v_null_text FROM tbl ORDER BY v_text)"
);
self.sql(&sql)
}
pub fn legacy_null_blob_aggregate<T: FromSql>(&self, hash: &str) -> Result<T> {
let sql = format!(
"SELECT {hash}(v_null_blob) FROM (SELECT v_null_blob FROM tbl ORDER BY v_text)"
);
self.sql(&sql)
}
pub fn sequence<T: FromSql>(&self, expr: &str, iterations: usize) -> Result<T> {
let sql = format!(
"
WITH RECURSIVE
seq(v) AS (
SELECT 1
UNION ALL
SELECT v + 1 FROM seq
LIMIT {iterations}
)
SELECT {expr} FROM seq"
);
self.sql(&sql)
}
pub fn seq_0<T: FromSql>(&self, expr: &str) -> Result<T> {
self.sequence(expr, 0)
}
pub fn seq_1<T: FromSql>(&self, expr: &str) -> Result<T> {
self.sequence(expr, 1)
}
pub fn seq_1000<T: FromSql>(&self, expr: &str) -> Result<T> {
self.sequence(expr, 1000)
}
}