Skip to content

Commit

Permalink
Add FNV and XXHASH hashing algorithms (#8)
Browse files Browse the repository at this point in the history
fixes #6
  • Loading branch information
nyurik authored Dec 21, 2023
1 parent 7b5ee83 commit bd202c4
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 22 deletions.
32 changes: 28 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "sqlite-hashes"
version = "0.6.0" # This value is also used in the README.md
description = "Hashing functions for SQLite with aggregation support: MD5, SHA1, SHA256, SHA512"
description = "Hashing functions for SQLite with aggregation support: MD5, SHA1, SHA256, SHA512, fnv1a, xxhash"
authors = ["Yuri Astrakhan <[email protected]>"]
repository = "https://github.com/nyurik/sqlite-hashes"
edition = "2021"
Expand All @@ -21,11 +21,11 @@ crate-type = ["cdylib"]
required-features = ["loadable_extension"]

[features]
default = ["trace", "aggregate", "window", "hex", "md5", "sha1", "sha224", "sha256", "sha384", "sha512"]
default = ["trace", "aggregate", "window", "hex", "md5", "sha1", "sha224", "sha256", "sha384", "sha512", "fnv", "xxhash"]
# Use this feature to build loadable extension.
# Assumes --no-default-features.
# Does not support windowing functionality yet.
default_loadable_extension = ["loadable_extension", "aggregate", "hex", "md5", "sha1", "sha224", "sha256", "sha384", "sha512"]
default_loadable_extension = ["loadable_extension", "aggregate", "hex", "md5", "sha1", "sha224", "sha256", "sha384", "sha512", "fnv", "xxhash"]
#
# Enable Trace Logging
trace = ["dep:hex", "dep:log"]
Expand All @@ -52,6 +52,8 @@ sha224 = ["dep:sha2"]
sha256 = ["dep:sha2"]
sha384 = ["dep:sha2"]
sha512 = ["dep:sha2"]
fnv = ["dep:noncrypto-digests", "noncrypto-digests?/fnv"]
xxhash = ["dep:noncrypto-digests", "noncrypto-digests?/xxh3", "noncrypto-digests?/xxh32", "noncrypto-digests?/xxh64"]

[dependencies]
hex = { version = "0.4", optional = true }
Expand All @@ -63,6 +65,7 @@ digest = "0.10.7"
md-5 = { version = "0.10.6", optional = true }
sha1 = { version = "0.10.6", optional = true }
sha2 = { version = "0.10.8", optional = true }
noncrypto-digests = { version = "0.3.0", optional = true }

[dev-dependencies]
cargo-husky = { version = "1", features = ["user-hooks"], default-features = false }
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[![CI build](https://github.com/nyurik/sqlite-hashes/actions/workflows/ci.yml/badge.svg)](https://github.com/nyurik/sqlite-hashes/actions)


Use this crate to add various hash functions to SQLite, including MD5, SHA1, SHA224, SHA256, SHA384, and SHA512.
Use this crate to add various hash functions to SQLite, including MD5, SHA1, SHA224, SHA256, SHA384, SHA512, FNV1a, XXHASH.

This crate uses [rusqlite](https://crates.io/crates/rusqlite) to add user-defined functions using static linking. Eventually it would be good to build dynamically loadable extension binaries usable from other languages (PRs welcome).

Expand Down Expand Up @@ -131,6 +131,8 @@ sqlite-hashes = { version = "0.6", default-features = false, features = ["hex",
* **sha256** - enable SHA256 hash support
* **sha384** - enable SHA384 hash support
* **sha512** - enable SHA512 hash support
* **fnv** - enable fnv1a hash support
* **xxhash** - enable xxh32, xxh64, xxh3_64, xxh3_128 hash support

## Development
* This project is easier to develop with [just](https://github.com/casey/just#readme), a modern alternative to `make`. Install it with `cargo install just`.
Expand Down
26 changes: 14 additions & 12 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -73,22 +73,24 @@ test-lib *ARGS: \
( test-one-lib "--no-default-features" "--features" "trace,hex,window,sha256" ) \
( test-one-lib "--no-default-features" "--features" "trace,hex,window,sha384" ) \
( test-one-lib "--no-default-features" "--features" "trace,hex,window,sha512" ) \
( test-one-lib "--no-default-features" "--features" "trace,hex,window,fnv" ) \
( test-one-lib "--no-default-features" "--features" "trace,hex,window,xxhash" ) \
\
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512" ) \
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,aggregate" ) \
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,window" ) \
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,fnv,xxhash" ) \
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,fnv,xxhash,aggregate" ) \
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,fnv,xxhash,window" ) \
\
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,hex" ) \
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,hex,aggregate" ) \
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,hex,window" ) \
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,fnv,xxhash,hex" ) \
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,fnv,xxhash,hex,aggregate" ) \
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,fnv,xxhash,hex,window" ) \
\
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,trace" ) \
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,trace,aggregate" ) \
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,trace,window" ) \
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,fnv,xxhash,trace" ) \
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,fnv,xxhash,trace,aggregate" ) \
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,fnv,xxhash,trace,window" ) \
\
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,hex,trace" ) \
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,hex,trace,aggregate" ) \
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,hex,trace,window" ) \
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,fnv,xxhash,hex,trace" ) \
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,fnv,xxhash,hex,trace,aggregate" ) \
( test-one-lib "--no-default-features" "--features" "md5,sha1,sha224,sha256,sha384,sha512,fnv,xxhash,hex,trace,window" ) \

test-ext:
./tests/test-ext.sh
Expand Down
24 changes: 24 additions & 0 deletions src/fnv.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use noncrypto_digests::Fnv;

use crate::rusqlite::{Connection, Result};

/// Register the `fnv1a` SQL function with the given `SQLite` connection.
/// The `fnv1a` function uses [Fowler–Noll–Vo hash function](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash) to compute the hash of the argument(s).
///
/// # Example
///
/// ```
/// # use sqlite_hashes::rusqlite::{Connection, Result};
/// # use sqlite_hashes::register_fnv_functions;
/// # fn main() -> Result<()> {
/// let db = Connection::open_in_memory()?;
/// register_fnv_functions(&db)?;
/// let hash: Vec<u8> = db.query_row("SELECT fnv1a('hello')", [], |r| r.get(0))?;
/// let expected = b"\xA4\x30\xD8\x46\x80\xAA\xBD\x0B";
/// assert_eq!(hash, expected);
/// # Ok(())
/// # }
/// ```
pub fn register_fnv_functions(conn: &Connection) -> Result<()> {
crate::scalar::create_hash_fn::<Fnv>(conn, "fnv1a")
}
34 changes: 33 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
feature = "sha256",
feature = "sha384",
feature = "sha512",
feature = "fnv",
feature = "xxhash",
)))]
compile_error!(
"At least one of these features must be enabled: md5,sha1,sha224,sha256,sha384,sha512"
"At least one of these features must be enabled: md5,sha1,sha224,sha256,sha384,sha512,fnv,xxhash"
);

/// Re-export of the [`rusqlite`](https://crates.io/crates/rusqlite) crate to avoid version conflicts.
Expand Down Expand Up @@ -60,6 +62,18 @@ mod sha512;
#[cfg(feature = "sha512")]
pub use crate::sha512::register_sha512_function;

#[cfg(feature = "fnv")]
mod fnv;

#[cfg(feature = "fnv")]
pub use crate::fnv::register_fnv_functions;

#[cfg(feature = "xxhash")]
mod xxhash;

#[cfg(feature = "xxhash")]
pub use crate::xxhash::register_xxhash_functions;

/// Register all hashing functions for the given `SQLite` connection.
/// This is a convenience function that calls all of the `register_*_function` functions.
/// Features must be enabled for the corresponding functions to be registered.
Expand Down Expand Up @@ -96,6 +110,20 @@ pub use crate::sha512::register_sha512_function;
/// let hash: String = db.query_row("SELECT sha512_hex('hello')", [], |r| r.get(0))?;
/// assert_eq!(hash, "9B71D224BD62F3785D96D46AD3EA3D73319BFBC2890CAADAE2DFF72519673CA72323C3D99BA5C11D7C7ACC6E14B8C5DA0C4663475C2E5C3ADEF46F73BCDEC043");
/// # }
/// # if cfg!(all(feature = "hex", feature = "fnv")) {
/// let hash: String = db.query_row("SELECT fnv1a_hex('hello')", [], |r| r.get(0))?;
/// assert_eq!(hash, "A430D84680AABD0B");
/// # }
/// # if cfg!(all(feature = "hex", feature = "xxhash")) {
/// let hash: String = db.query_row("SELECT xxh32_hex('hello')", [], |r| r.get(0))?;
/// assert_eq!(hash, "FB0077F9");
/// let hash: String = db.query_row("SELECT xxh64_hex('hello')", [], |r| r.get(0))?;
/// assert_eq!(hash, "26C7827D889F6DA3");
/// let hash: String = db.query_row("SELECT xxh3_64_hex('hello')", [], |r| r.get(0))?;
/// assert_eq!(hash, "9555E8555C62DCFD");
/// let hash: String = db.query_row("SELECT xxh3_128_hex('hello')", [], |r| r.get(0))?;
/// assert_eq!(hash, "B5E9C1AD071B3E7FC779CFAA5E523818");
/// # }
/// # Ok(())
/// # }
/// ```
Expand All @@ -112,6 +140,10 @@ pub fn register_hash_functions(conn: &Connection) -> Result<()> {
register_sha384_function(conn)?;
#[cfg(feature = "sha512")]
register_sha512_function(conn)?;
#[cfg(feature = "fnv")]
register_fnv_functions(conn)?;
#[cfg(feature = "xxhash")]
register_xxhash_functions(conn)?;

Ok(())
}
17 changes: 16 additions & 1 deletion src/scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,15 @@ pub trait NamedDigest: Digest {

macro_rules! digest_names {
($($typ:ty => $name:literal),* $(,)?) => {
digest_names!(
$(
$typ => $name @ $name,
)*
);
};
($($typ:ty => $name:literal @ $feature:literal),* $(,)?) => {
$(
#[cfg(feature = $name)]
#[cfg(feature = $feature)]
impl NamedDigest for $typ {
fn name() -> &'static str {
$name
Expand All @@ -45,6 +52,14 @@ digest_names! {
sha2::Sha512 => "sha512",
}

digest_names! {
noncrypto_digests::Fnv => "fnv1a" @ "fnv",
noncrypto_digests::Xxh32 => "xxh32" @ "xxhash",
noncrypto_digests::Xxh64 => "xxh64" @ "xxhash",
noncrypto_digests::Xxh3_64 => "xxh3_64" @ "xxhash",
noncrypto_digests::Xxh3_128 => "xxh3_128" @ "xxhash",
}

pub(crate) fn create_hash_fn<T: NamedDigest + Clone + UnwindSafe + RefUnwindSafe + 'static>(
conn: &Connection,
fn_name: &str,
Expand Down
38 changes: 38 additions & 0 deletions src/xxhash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use noncrypto_digests::{Xxh32, Xxh3_128, Xxh3_64, Xxh64};

use crate::rusqlite::{Connection, Result};

/// Register `xxh32`, `xxh64`, `xxh3_64`, `xxh3_128`, `xxh3_64` SQL functions with the given `SQLite` connection.
/// The functions use [Rust xxhash implementation](https://github.com/DoumanAsh/xxhash-rust) to compute the hash of the argument(s) using zero as the seed value.
///
/// # Example
///
/// ```
/// # // Use Python to convert:
/// # // print('"\\x' + '\\x'.join([f"{v:02X}" for v in [251, 0, 119, 249]])+'"')
/// # use sqlite_hashes::rusqlite::{Connection, Result};
/// # use sqlite_hashes::register_xxhash_functions;
/// # fn main() -> Result<()> {
/// let db = Connection::open_in_memory()?;
/// register_xxhash_functions(&db)?;
/// let hash: Vec<u8> = db.query_row("SELECT xxh32('hello')", [], |r| r.get(0))?;
/// let expected = b"\xFB\x00\x77\xF9";
/// assert_eq!(hash, expected);
/// let hash: Vec<u8> = db.query_row("SELECT xxh64('hello')", [], |r| r.get(0))?;
/// let expected = b"\x26\xC7\x82\x7D\x88\x9F\x6D\xA3";
/// assert_eq!(hash, expected);
/// let hash: Vec<u8> = db.query_row("SELECT xxh3_64('hello')", [], |r| r.get(0))?;
/// let expected = b"\x95\x55\xE8\x55\x5C\x62\xDC\xFD";
/// assert_eq!(hash, expected);
/// let hash: Vec<u8> = db.query_row("SELECT xxh3_128('hello')", [], |r| r.get(0))?;
/// let expected = b"\xb5\xe9\xc1\xad\x07\x1b\x3e\x7f\xc7\x79\xcf\xaa\x5e\x52\x38\x18";
/// assert_eq!(hash, expected);
/// # Ok(())
/// # }
/// ```
pub fn register_xxhash_functions(conn: &Connection) -> Result<()> {
crate::scalar::create_hash_fn::<Xxh32>(conn, "xxh32")?;
crate::scalar::create_hash_fn::<Xxh64>(conn, "xxh64")?;
crate::scalar::create_hash_fn::<Xxh3_64>(conn, "xxh3_64")?;
crate::scalar::create_hash_fn::<Xxh3_128>(conn, "xxh3_128")
}
19 changes: 19 additions & 0 deletions tests/_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ fn hasher() {
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 = "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");
}
}

/// Create macros like `md5!` asserting that first expression equals to the hash of the second one.
Expand Down Expand Up @@ -130,6 +139,11 @@ hash_macros!(
"sha256" sha256 sha2::Sha256,
"sha384" sha384 sha2::Sha384,
"sha512" sha512 sha2::Sha512,
"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 {
Expand All @@ -145,6 +159,11 @@ macro_rules! test_all {
sha256!( $conn.$func(&format!("sha256{suffix}")), $($any)* );
sha384!( $conn.$func(&format!("sha384{suffix}")), $($any)* );
sha512!( $conn.$func(&format!("sha512{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)* );
}};
}

Expand Down
5 changes: 5 additions & 0 deletions tests/test-ext.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,8 @@ test_hash "sha224" "A7470858E79C282BC2F6ADFD831B132672DFD1224C1E78CBF5BCD057"
test_hash "sha256" "5994471ABB01112AFCC18159F6CC74B4F511B99806DA59B3CAF5A9C173CACFC5"
test_hash "sha384" "0FA76955ABFA9DAFD83FACCA8343A92AA09497F98101086611B0BFA95DBC0DCC661D62E9568A5A032BA81960F3E55D4A"
test_hash "sha512" "3627909A29C31381A071EC27F7C9CA97726182AED29A7DDD2E54353322CFB30ABB9E3A6DF2AC2C20FE23436311D678564D0C8D305930575F60E2D3D048184D79"
test_hash "fnv1a" "E575E8883C0F89F8"
test_hash "xxh32" "B30D56B4"
test_hash "xxh64" "C6F2D2DD0AD64FB6"
test_hash "xxh3_64" "F34099EDE96B5581"
test_hash "xxh3_128" "4AF3DA69F61E14CF26F4C14B6B6BFDB4"

0 comments on commit bd202c4

Please sign in to comment.