use crate::{
CharType, Error, Size, SizeExt, Type,
common::{SQL_TYPE_TEXT, SQL_TYPES, SqlType, check_constraint_violation},
rusqlite::{Connection, ToSql, Transaction},
};
use cfg_if::cfg_if;
use std::{borrow::Borrow, collections::BTreeMap, marker::PhantomData, sync::LazyLock};
static SQL_CREATE_TBL: LazyLock<BTreeMap<SqlType, String>> = LazyLock::new(|| {
let mut map = BTreeMap::<SqlType, String>::new();
for sql_type in SQL_TYPES {
cfg_if! {
if #[cfg(feature = "with_rowid")] {
map.insert(sql_type, format!("CREATE TABLE data (key {sql_type} PRIMARY KEY NOT NULL);"));
} else {
map.insert(sql_type, format!("CREATE TABLE data (key {sql_type} PRIMARY KEY NOT NULL) WITHOUT ROWID;"));
}
}
}
map
});
cfg_if! {
if #[cfg(feature = "with_rowid")] {
const SQL_CREATE_NOC: &str = "CREATE TABLE data (key TEXT PRIMARY KEY NOT NULL COLLATE NOCASE);";
} else {
const SQL_CREATE_NOC: &str = "CREATE TABLE data (key TEXT PRIMARY KEY NOT NULL COLLATE NOCASE) WITHOUT ROWID;";
}
}
const SQL_COUNT_KEYS: &str = "SELECT COUNT(*) FROM data;";
const SQL_INSERT_KEY: &str = "INSERT INTO data (key) VALUES (?1);";
const SQL_EXISTS_KEY: &str = "SELECT 1 FROM data WHERE key = ?1 LIMIT 1;";
const SQL_EXISTS_NOC: &str = "SELECT 1 FROM data WHERE key COLLATE NOCASE = ? LIMIT 1;";
const SQL_QUERY_KEYS: &str = "SELECT key FROM data;";
const SQL_WHERE_KEYS: &str = "SELECT key FROM data WHERE key LIKE ?1 ESCAPE '\\';";
const SQL_DELETE_KEY: &str = "DELETE FROM data WHERE key = ?1;";
const SQL_DELETE_NOC: &str = "DELETE FROM data WHERE key COLLATE NOCASE = ?1;";
const SQL_DELETE_ALL: &str = "DELETE FROM data;";
pub struct SQLiteSet<T>
where
T: Type,
{
_type: PhantomData<T>,
connection: Connection,
}
impl<T> SQLiteSet<T>
where
T: Type,
{
#[inline]
pub fn new() -> Result<Self, Error> {
Ok(Self { _type: PhantomData, connection: Self::initialize_connection::<false>()? })
}
#[inline]
pub fn with_nocase() -> Result<Self, Error>
where
T: CharType,
{
Ok(Self { _type: PhantomData, connection: Self::initialize_connection::<true>()? })
}
#[inline]
fn initialize_connection<const NO_CASE: bool>() -> Result<Connection, Error> {
let connection = Connection::open_in_memory()?;
connection.pragma_update(None, "journal_mode", "OFF")?;
connection.pragma_update(None, "synchronous", "OFF")?;
connection.pragma_update(None, "temp_store", "MEMORY")?;
if !NO_CASE {
connection.execute(SQL_CREATE_TBL.get(&T::SQL_TYPE).unwrap(), [])?;
} else {
assert_eq!(T::SQL_TYPE, SQL_TYPE_TEXT);
connection.execute(SQL_CREATE_NOC, [])?;
}
Ok(connection)
}
pub fn transaction(&mut self) -> Result<SQLiteSetTransaction<'_, T>, Error> {
SQLiteSetTransaction::from(&mut self.connection)
}
#[inline]
pub fn insert<Q>(&mut self, key: &Q) -> Result<bool, Error>
where
T: Borrow<Q>,
Q: ?Sized + ToSql,
{
let mut statement = self.connection.prepare_cached(SQL_INSERT_KEY)?;
match statement.execute([key]) {
Ok(_) => Ok(true),
Err(error) => check_constraint_violation(error),
}
}
#[inline]
pub fn contains<Q>(&self, key: &Q) -> Result<bool, Error>
where
T: Borrow<Q>,
Q: ?Sized + ToSql,
{
let mut statement = self.connection.prepare_cached(SQL_EXISTS_KEY)?;
Ok(statement.exists([key])?)
}
#[inline]
pub fn contains_nocase<Q>(&self, key: &Q) -> Result<bool, Error>
where
T: Borrow<Q> + CharType,
Q: ?Sized + ToSql,
{
let mut statement = self.connection.prepare_cached(SQL_EXISTS_NOC)?;
Ok(statement.exists([key])?)
}
#[inline]
pub fn remove<Q>(&mut self, key: &Q) -> Result<bool, Error>
where
T: Borrow<Q>,
Q: ?Sized + ToSql,
{
let mut statement = self.connection.prepare_cached(SQL_DELETE_KEY)?;
Ok(statement.execute([key])? != 0)
}
#[inline]
pub fn remove_nocase<Q>(&mut self, key: &Q) -> Result<bool, Error>
where
T: Borrow<Q> + CharType,
Q: ?Sized + ToSql,
{
let mut statement = self.connection.prepare_cached(SQL_DELETE_NOC)?;
Ok(statement.execute([key])? != 0)
}
#[inline]
pub fn for_each<F>(&self, mut callback: F) -> Result<(), Error>
where
F: FnMut(T),
{
let mut statement = self.connection.prepare_cached(SQL_QUERY_KEYS)?;
for key in statement.query_map([], |row| row.get(0))? {
callback(key?);
}
Ok(())
}
#[inline]
pub fn find<P, Q>(&self, predicate: P) -> Result<Option<T>, Error>
where
P: Fn(&Q) -> bool,
T: Borrow<Q>,
Q: ?Sized,
{
let mut statement = self.connection.prepare_cached(SQL_QUERY_KEYS)?;
for key in statement.query_map([], |row| row.get(0))? {
let key: T = key?;
if predicate(key.borrow()) {
return Ok(Some(key));
}
}
Ok(None)
}
#[inline]
pub fn filter<F, Q>(&self, pattern: &Q, mut callback: F) -> Result<(), Error>
where
F: FnMut(T),
T: Borrow<Q> + CharType,
Q: ?Sized + ToSql,
{
let mut statement = self.connection.prepare_cached(SQL_WHERE_KEYS)?;
for key in statement.query_map([pattern], |row| row.get(0))? {
callback(key?);
}
Ok(())
}
#[inline]
pub fn len(&self) -> Result<Size, Error> {
let mut statement = self.connection.prepare_cached(SQL_COUNT_KEYS)?;
let count: i64 = statement.query_one([], |row| row.get(0))?;
Ok(Size::from_i64(count))
}
#[inline]
pub fn is_empty(&self) -> Result<bool, Error> {
Ok(self.len()? == 0)
}
#[inline]
pub fn clear(&mut self) -> Result<(), Error> {
let mut statement = self.connection.prepare_cached(SQL_DELETE_ALL)?;
statement.execute([])?;
Ok(())
}
}
pub struct SQLiteSetTransaction<'a, T>
where
T: Type,
{
_type: PhantomData<T>,
transaction: Transaction<'a>,
}
impl<'a, T> SQLiteSetTransaction<'a, T>
where
T: Type,
{
#[inline]
fn from(connection: &'a mut Connection) -> Result<Self, Error> {
let mut transaction = connection.transaction()?;
transaction.set_drop_behavior(rusqlite::DropBehavior::Commit);
Ok(Self { _type: PhantomData, transaction })
}
#[inline]
pub fn commit(self) {}
#[inline]
pub fn insert<Q>(&mut self, key: &Q) -> Result<bool, Error>
where
T: Borrow<Q>,
Q: ?Sized + ToSql,
{
let mut statement = self.transaction.prepare_cached(SQL_INSERT_KEY)?;
match statement.execute([key]) {
Ok(_) => Ok(true),
Err(error) => check_constraint_violation(error),
}
}
#[inline]
pub fn contains<Q>(&self, key: &Q) -> Result<bool, Error>
where
T: Borrow<Q>,
Q: ?Sized + ToSql,
{
let mut statement = self.transaction.prepare_cached(SQL_EXISTS_KEY)?;
Ok(statement.exists([key])?)
}
#[inline]
pub fn contains_nocase<Q>(&self, key: &Q) -> Result<bool, Error>
where
T: Borrow<Q> + CharType,
Q: ?Sized + ToSql,
{
let mut statement = self.transaction.prepare_cached(SQL_EXISTS_NOC)?;
Ok(statement.exists([key])?)
}
#[inline]
pub fn remove<Q>(&mut self, key: &Q) -> Result<bool, Error>
where
T: Borrow<Q>,
Q: ?Sized + ToSql,
{
let mut statement = self.transaction.prepare_cached(SQL_DELETE_KEY)?;
Ok(statement.execute([key])? != 0)
}
#[inline]
pub fn remove_nocase<Q>(&mut self, key: &Q) -> Result<bool, Error>
where
T: Borrow<Q> + CharType,
Q: ?Sized + ToSql,
{
let mut statement = self.transaction.prepare_cached(SQL_DELETE_NOC)?;
Ok(statement.execute([key])? != 0)
}
#[inline]
pub fn for_each<F>(&self, mut callback: F) -> Result<(), Error>
where
F: FnMut(T),
{
let mut statement = self.transaction.prepare_cached(SQL_QUERY_KEYS)?;
for key in statement.query_map([], |row| row.get(0))? {
callback(key?);
}
Ok(())
}
#[inline]
pub fn find<P, Q>(&self, predicate: P) -> Result<Option<T>, Error>
where
P: Fn(&Q) -> bool,
T: Borrow<Q>,
Q: ?Sized,
{
let mut statement = self.transaction.prepare_cached(SQL_QUERY_KEYS)?;
for key in statement.query_map([], |row| row.get(0))? {
let key: T = key?;
if predicate(key.borrow()) {
return Ok(Some(key));
}
}
Ok(None)
}
#[inline]
pub fn filter<F, Q>(&self, pattern: &Q, mut callback: F) -> Result<(), Error>
where
F: FnMut(T),
T: Borrow<Q> + CharType,
Q: ?Sized + ToSql,
{
let mut statement = self.transaction.prepare_cached(SQL_WHERE_KEYS)?;
for key in statement.query_map([pattern], |row| row.get(0))? {
callback(key?);
}
Ok(())
}
#[inline]
pub fn len(&self) -> Result<Size, Error> {
let mut statement = self.transaction.prepare_cached(SQL_COUNT_KEYS)?;
let count: i64 = statement.query_one([], |row| row.get(0))?;
Ok(Size::from_i64(count))
}
#[inline]
pub fn is_empty(&self) -> Result<bool, Error> {
Ok(self.len()? == 0)
}
#[inline]
pub fn clear(&mut self) -> Result<(), Error> {
let mut statement = self.transaction.prepare_cached(SQL_DELETE_ALL)?;
statement.execute([])?;
Ok(())
}
}