Skip to main content

sqlite_containers/
common.rs

1// Common Data Types
2// This file is part of the SQLite-based containers for Rust project (sqlite-containers)
3// SPDX-License-Identifier: Unlicense
4
5use crate::rusqlite::{
6    Error as RusqliteError, ErrorCode,
7    types::{FromSql, ToSql, Type as RusqliteType},
8};
9use cfg_if::cfg_if;
10use std::fmt::Display;
11
12/// Binary Large Object (BLOB), represented as a [`Vec<u8>`](std::vec::Vec).
13pub type Blob = Vec<u8>;
14
15// ---------------------------------------------------------------------------
16// Sealed Type
17// ---------------------------------------------------------------------------
18
19trait Sealed {}
20
21macro_rules! impl_sealed {
22    ($($t:ty),*) => {
23        $(
24            impl Sealed for $t {}
25        )*
26    };
27}
28
29impl_sealed!(i32, u32, i64, String, Blob);
30
31// ---------------------------------------------------------------------------
32// SQL Type Holder
33// ---------------------------------------------------------------------------
34
35#[derive(Eq, PartialEq, Copy, Clone, Debug)]
36pub struct SqlType(pub RusqliteType);
37
38impl SqlType {
39    #[inline(always)]
40    pub const fn new(value: RusqliteType) -> Self {
41        Self(value)
42    }
43}
44
45impl Ord for SqlType {
46    #[inline]
47    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
48        (self.0 as usize).cmp(&(other.0 as usize))
49    }
50}
51
52impl PartialOrd for SqlType {
53    #[inline]
54    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
55        Some(self.cmp(other))
56    }
57}
58
59impl Display for SqlType {
60    #[inline]
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        f.write_str(&self.0.to_string().to_ascii_uppercase())
63    }
64}
65
66pub const SQL_TYPE_TEXT: SqlType = SqlType::new(RusqliteType::Text);
67pub static SQL_TYPES: [SqlType; 3usize] = [SqlType::new(RusqliteType::Integer), SqlType::new(RusqliteType::Text), SqlType::new(RusqliteType::Blob)];
68
69// ---------------------------------------------------------------------------
70// Key or Value Type
71// ---------------------------------------------------------------------------
72
73/// The type of the keys or values that are stored in an [`SQLiteMap`](crate::SQLiteMap) or an [`SQLiteSet`](crate::SQLiteSet).
74///
75/// Currently, the following types are supported:
76///
77/// Type                              | SQLite storage datatype
78/// --------------------------------- | -----------------------
79/// **`i32`**                         | `INTEGER`
80/// **`u32`**                         | `INTEGER`
81/// **`i64`**                         | `INTEGER`
82/// **`Vec<u8>`**, alias **[`Blob`]** | `BLOB`
83/// **[`String`]**                    | `TEXT`
84///
85/// &nbsp;
86///
87/// <div class="warning">
88///
89/// Type `u64` is **not** currently supported directly, because SQLite's `INTEGER` datatype is (at most) 64-Bit in size and is *always* signed; it can **not** store values greater than `i64::MAX`. To store `u64` keys or values, please use `value as i64` to convert the keys or value to `i64` before insertion. This can be reverted, after a key or value has been read, with `value as u64`.
90///
91/// </div>
92#[allow(private_bounds)]
93pub trait Type: FromSql + ToSql + Sealed {
94    /// The SQLite storage datatype that corresponds to this type.
95    const SQL_TYPE: SqlType;
96}
97
98macro_rules! impl_type {
99    ($type:ty, $sql_type:ident) => {
100        impl Type for $type {
101            const SQL_TYPE: SqlType = SqlType::new(RusqliteType::$sql_type);
102        }
103    };
104}
105
106impl_type!(i32, Integer);
107impl_type!(u32, Integer);
108impl_type!(i64, Integer);
109impl_type!(Blob, Blob);
110impl_type!(String, Text);
111
112// ---------------------------------------------------------------------------
113// Character-based Types
114// ---------------------------------------------------------------------------
115
116/// Represents a character-based type, e.g., [`String`].
117///
118/// Keys of these types can be treated case-sensitive (default) or case-insensitive.
119pub trait CharType {}
120
121macro_rules! impl_chartype {
122    ($($t:ty),*) => {
123        $(
124            impl CharType for $t {}
125        )*
126    };
127}
128
129impl_chartype!(String);
130
131// ---------------------------------------------------------------------------
132// Size Type
133// ---------------------------------------------------------------------------
134
135cfg_if! {
136    if #[cfg(target_pointer_width = "64")] {
137        /// The "size" type used by [`len()`](crate::SQLiteSet::len), equal to `usize` on this platform.
138        pub type Size = usize;
139    } else {
140        /// The "size" type used by [`len()`](crate::SQLiteSet::len), equal to `u64` on this platform.
141        pub type Size = u64;
142    }
143}
144
145/// Provides useful helper functions for the [`Size`](crate::Size) type.
146pub trait SizeExt {
147    fn from_i64(value: i64) -> Self;
148    fn from_u64(value: u64) -> Self;
149    fn to_u64(self) -> u64;
150    fn to_usize(self) -> Option<usize>;
151}
152
153impl SizeExt for Size {
154    #[inline(always)]
155    fn from_i64(value: i64) -> Self {
156        value.try_into().unwrap_or_default()
157    }
158
159    #[inline(always)]
160    fn from_u64(value: u64) -> Self {
161        value as Self
162    }
163
164    #[allow(clippy::unnecessary_cast)]
165    #[inline(always)]
166    fn to_u64(self) -> u64 {
167        self as u64
168    }
169
170    #[inline(always)]
171    fn to_usize(self) -> Option<usize> {
172        cfg_if! {
173            if #[cfg(target_pointer_width = "64")] {
174                Some(self)
175            } else {
176                self.try_into().ok()
177            }
178        }
179    }
180}
181
182// ---------------------------------------------------------------------------
183// SQLite Error
184// ---------------------------------------------------------------------------
185
186/// Indicates that the operation failed due to an SQLite error.
187///
188/// Container operations are generally infallible. However, because our containers are backed by SQLite databases, they may still fail due to an internal SQLite error. Such errors can indicate a bug, but may also occur in out-of-memory situations.
189///
190/// Wraps the underlying [`RusqliteError`](crate::rusqlite::Error).
191#[allow(dead_code)]
192#[derive(Debug)]
193pub struct Error(RusqliteError);
194
195impl Error {
196    pub fn into_inner(self) -> RusqliteError {
197        self.0
198    }
199}
200
201impl From<RusqliteError> for Error {
202    #[inline]
203    fn from(error: RusqliteError) -> Self {
204        Self(error)
205    }
206}
207
208#[inline]
209pub fn check_constraint_violation(error: RusqliteError) -> Result<bool, Error> {
210    match error {
211        RusqliteError::SqliteFailure(sql_error, _) => match sql_error.code {
212            ErrorCode::ConstraintViolation => Ok(false),
213            _ => Err(error.into()),
214        },
215        _ => Err(error.into()),
216    }
217}
218
219// ---------------------------------------------------------------------------
220// Tests
221// ---------------------------------------------------------------------------
222
223#[cfg(test)]
224mod test {
225    use super::*;
226    use crate::rusqlite::ffi::{Error as FfiError, ErrorCode as FfiErrorCode, SQLITE_CONSTRAINT, SQLITE_NOMEM};
227    use std::path::PathBuf;
228
229    #[cfg(target_pointer_width = "64")]
230    #[test]
231    fn test_size_p64() {
232        assert_eq!(Size::from_i64(i64::MAX), i64::MAX as usize);
233        assert_eq!(Size::from_i64(1i64), 1usize);
234        assert_eq!(Size::from_i64(0i64), usize::MIN);
235        assert_eq!(Size::from_i64(-1i64), usize::MIN);
236        assert_eq!(Size::from_i64(i64::MIN), usize::MIN);
237
238        assert_eq!(Size::from_u64(u64::MAX), usize::MAX);
239        assert_eq!(Size::from_u64(1u64), 1usize);
240        assert_eq!(Size::from_u64(u64::MIN), usize::MIN);
241
242        assert_eq!(Size::from_i64(i64::MAX).to_u64(), i64::MAX as u64);
243        assert_eq!(Size::from_i64(i64::MAX).to_usize(), Some(i64::MAX as usize));
244    }
245
246    #[cfg(not(target_pointer_width = "64"))]
247    #[test]
248    fn test_size_p32() {
249        assert_eq!(Size::from_i64(i64::MAX), i64::MAX as u64);
250        assert_eq!(Size::from_i64(1i64), 1u64);
251        assert_eq!(Size::from_i64(0i64), u64::MIN);
252        assert_eq!(Size::from_i64(-1i64), u64::MIN);
253        assert_eq!(Size::from_i64(i64::MIN), u64::MIN);
254
255        assert_eq!(Size::from_u64(u64::MAX), u64::MAX);
256        assert_eq!(Size::from_u64(1u64), 1u64);
257        assert_eq!(Size::from_u64(u64::MIN), u64::MIN);
258
259        assert_eq!(Size::from_i64(i64::MAX).to_u64(), i64::MAX as u64);
260        assert_eq!(Size::from_i64(usize::MAX as i64).to_usize(), Some(usize::MAX));
261        assert!(Size::from_i64(i64::MAX).to_usize().is_none());
262    }
263
264    #[test]
265    fn test_sql_type() {
266        const ALL_TYPES: [RusqliteType; 3usize] = [RusqliteType::Integer, RusqliteType::Blob, RusqliteType::Text];
267        for native_type in ALL_TYPES {
268            let sql_type = SqlType::new(native_type);
269            assert_eq!(sql_type.0, native_type);
270            for other_type in ALL_TYPES {
271                let other_type = SqlType::new(other_type);
272                assert_eq!(sql_type.partial_cmp(&other_type).unwrap(), sql_type.cmp(&other_type));
273            }
274        }
275    }
276
277    #[test]
278    fn test_error() {
279        let error_1 = Error::from(RusqliteError::InvalidQuery);
280        assert_eq!(error_1.into_inner(), RusqliteError::InvalidQuery);
281
282        let error_2 = Error::from(RusqliteError::InvalidPath(PathBuf::from("filename.txt")));
283        assert!(matches!(error_2.into_inner(), RusqliteError::InvalidPath(_)));
284
285        let error_3 = Error::from(RusqliteError::SqliteFailure(FfiError::new(SQLITE_NOMEM), Some("Memory allocation fail!".to_owned())));
286        assert!(matches!(error_3.into_inner(), RusqliteError::SqliteFailure(FfiError { code: FfiErrorCode::OutOfMemory, .. }, Some(_))));
287    }
288
289    #[test]
290    fn test_check_constraint_violation() {
291        let error_1 = check_constraint_violation(RusqliteError::InvalidQuery);
292        assert!(matches!(error_1, Err(Error(..))));
293
294        let error_2 = check_constraint_violation(RusqliteError::SqliteFailure(FfiError::new(SQLITE_CONSTRAINT), None));
295        assert!(matches!(error_2, Ok(false)));
296
297        let error_3 = check_constraint_violation(RusqliteError::SqliteFailure(FfiError::new(SQLITE_NOMEM), None));
298        assert!(matches!(error_3, Err(Error(..))));
299    }
300}