sqlfuncs 0.6.0

Scalar functions for use in SQLite through rusqlite.
Documentation
//! A collection of hashing functions.
//!
//! Hashing functions that take in an algorigm name accept the following names:
//! `sha2/224`, `sha2/256`, `sha2/384`, `sha2/512`, `sha3/224`, `sha3/256`,
//! `sha3/384`, `sha3/512`

use digest::Digest;

use rusqlite::{Connection, Error, functions::FunctionFlags};


fn mkhasher(algo: &str) -> Result<Box<dyn digest::DynDigest>, Error> {
  match algo {
    "sha2/224" => Ok(Box::new(sha2::Sha224::new())),
    "sha2/256" => Ok(Box::new(sha2::Sha256::new())),
    "sha2/384" => Ok(Box::new(sha2::Sha384::new())),
    "sha2/512" => Ok(Box::new(sha2::Sha512::new())),

    "sha3/224" => Ok(Box::new(sha3::Sha3_224::new())),
    "sha3/256" => Ok(Box::new(sha3::Sha3_256::new())),
    "sha3/384" => Ok(Box::new(sha3::Sha3_384::new())),
    "sha3/512" => Ok(Box::new(sha3::Sha3_512::new())),

    _ => Err(Error::UserFunctionError("Bad hash algo".into()))?
  }
}

/// Add a `hashstr()` SQL function to the connection object.
///
/// The SQL function `hashstr()` takes two arguments:
/// 1. The algorithm that will be used to hash the input.
/// 2. The input string to hash.
///
/// Currently only the algorithm `sha2/256` is supported.
///
/// ```
/// use rusqlite::Connection;
/// use sqlfuncs::hashing::hashstr;
///
/// let conn = Connection::open_in_memory().unwrap();
/// hashstr(&conn).unwrap();
/// let instr = "hello, world";
/// let hstr: String = conn.query_row_and_then(
///   "SELECT lower(hex(hashstr('sha2/256', ?)));",
///   [instr],
///   |row| {
///     row.get(0)
///   }
///   ).unwrap();
/// assert_eq!(&hstr[..8], "09ca7e4e");
/// ```
///
/// ## SQLite function properties
/// - Deterministic
/// - Innocuous
/// - UTF8
#[allow(clippy::missing_errors_doc)]
pub fn hashstr(conn: &Connection) -> Result<(), Error> {
  conn.create_scalar_function(
    "hashstr",
    2,
    FunctionFlags::SQLITE_UTF8
      | FunctionFlags::SQLITE_INNOCUOUS
      | FunctionFlags::SQLITE_DETERMINISTIC,
    move |ctx| {
      //assert_eq!(ctx.len(), 2, "called with unexpected number of
      // arguments");
      let algo = ctx.get::<String>(0)?.to_lowercase();
      let s = ctx.get::<String>(1)?;

      let mut hasher = mkhasher(algo.as_ref())?;
      hasher.update(s.as_bytes());
      let result = hasher.finalize();

      Ok(result[..].to_vec())
    }
  )
}

/// Add a `hashblob()` SQL function to the connection object.
///
/// The SQL function `hashblob()` takes two arguments:
/// 1. The algorithm that will be used to hash the input.
/// 2. The input blob to hash.
///
/// Currently only the algorithm `sha2/256` is supported.
///
/// ```
/// use rusqlite::Connection;
/// use sqlfuncs::hashing::hashblob;
///
/// let conn = Connection::open_in_memory().unwrap();
/// hashblob(&conn).unwrap();
/// let buf = b"hello, world";
/// let hstr: String = conn.query_row_and_then(
///   "SELECT lower(hex(hashblob('sha2/256', ?)));",
///   [&buf],
///   |row| {
///     row.get(0)
///   }
///   ).unwrap();
/// assert_eq!(&hstr[..8], "09ca7e4e");
/// ```
///
/// ## SQLite function properties
/// - Deterministic
/// - Innocuous
/// - UTF8
#[allow(clippy::missing_errors_doc)]
pub fn hashblob(conn: &Connection) -> Result<(), Error> {
  conn.create_scalar_function(
    "hashblob",
    2,
    FunctionFlags::SQLITE_UTF8
      | FunctionFlags::SQLITE_INNOCUOUS
      | FunctionFlags::SQLITE_DETERMINISTIC,
    move |ctx| {
      //assert_eq!(ctx.len(), 2, "called with unexpected number of
      // arguments");
      let algo = ctx.get::<String>(0)?.to_lowercase();
      let blob = ctx.get::<Vec<u8>>(1)?;

      let mut hasher = mkhasher(algo.as_ref())?;
      hasher.update(&blob);
      let result = hasher.finalize();

      Ok(result[..].to_vec())
    }
  )
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :