keyvaluedb-sqlite 0.1.7

A key-value SQLite database that implements the `KeyValueDB` trait
Documentation
static HEXCHARS: [char; 16] = [
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
];
static ENC_CHAR: char = '$';
static ENC_BYTE: u8 = b'$';
static ESCAPE_CHAR: char = '\\';
static ESCAPE_BYTE: u8 = b'\\';

pub fn from_hex_char(h: u8) -> Result<u8, ()> {
    if h.is_ascii_digit() {
        Ok(h - b'0')
    } else if (b'a'..=b'f').contains(&h) {
        Ok(h - b'a' + 10)
    } else if (b'A'..=b'F').contains(&h) {
        Ok(h - b'A' + 10)
    } else {
        Err(())
    }
}

pub fn key_to_text(key: &[u8]) -> String {
    let mut outlen: usize = 0;
    for &x in key {
        if (32..=126).contains(&x) && x != ENC_BYTE {
            outlen += 1;
        } else {
            outlen += 3;
        }
    }
    let mut out = String::with_capacity(outlen);
    for &x in key {
        if (32..=126).contains(&x) && x != ENC_BYTE {
            out.push(char::from_u32(x as u32).unwrap());
        } else {
            out.push(ENC_CHAR);
            out.push(HEXCHARS[((x >> 4) & 0x0F) as usize]);
            out.push(HEXCHARS[(x & 0x0F) as usize]);
        }
    }
    out
}

fn is_like_special(ch: u8) -> bool {
    ch == b'_' || ch == b'%'
}

pub fn like_key_to_text(key: &[u8]) -> String {
    let mut outlen: usize = 0;
    for &x in key {
        if (32..=126).contains(&x) && x != ENC_BYTE {
            if is_like_special(x) || x == ESCAPE_BYTE {
                outlen += 2;
            } else {
                outlen += 1;
            }
        } else {
            outlen += 3;
        }
    }
    let mut out = String::with_capacity(outlen);
    for &x in key {
        if (32..=126).contains(&x) && x != ENC_BYTE {
            if is_like_special(x) || x == ESCAPE_BYTE {
                out.push(ESCAPE_CHAR);
            }
            out.push(char::from_u32(x as u32).unwrap());
        } else {
            out.push(ENC_CHAR);
            out.push(HEXCHARS[((x >> 4) & 0x0F) as usize]);
            out.push(HEXCHARS[(x & 0x0F) as usize]);
        }
    }
    out
}

pub fn text_to_key(text: &str) -> Result<Vec<u8>, ()> {
    let mut outlen: usize = 0;
    let bytes = text.as_bytes();
    let mut i = 0usize;
    while i < bytes.len() {
        let x = bytes[i];
        if x == ENC_BYTE {
            i += 3;
        } else {
            i += 1;
        }
        outlen += 1;
    }
    let mut out: Vec<u8> = Vec::with_capacity(outlen);
    i = 0;
    while i < bytes.len() {
        let x = bytes[i];
        if x == ENC_BYTE {
            let h1 = bytes.get(i + 1).ok_or(())?;
            let h2 = bytes.get(i + 2).ok_or(())?;
            let b: u8 = (from_hex_char(*h1)? << 4) | from_hex_char(*h2)?;
            out.push(b);
            i += 3;
        } else {
            out.push(x);
            i += 1;
        }
    }
    Ok(out)
}

pub fn get_column_table_name(column: u32) -> String {
    format!("column_{}", column)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn check_key_text() {
        assert_eq!(key_to_text(b"Test\xFFKey\x001!\xC3"), "Test$FFKey$001!$C3");
        assert_eq!(
            b"Test\xFFKey\x001!\xC3".to_vec(),
            text_to_key("Test$FFKey$001!$C3").unwrap()
        );
        assert!(text_to_key("Test$FFKey$001!$C").is_err());
        assert!(text_to_key("Test$FFKey$001!$G3").is_err());
        assert!(text_to_key("Test$FFKey$001!$CG").is_err());
        assert!(text_to_key("Test$FFKey$001!$").is_err());
        assert_eq!(
            like_key_to_text(b"Test_\xFFKey%\x001!\xC3"),
            "Test\\_$FFKey\\%$001!$C3"
        );
    }
}