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::{
6    rusqlite::{
7        Error as RusqliteError, ErrorCode, OpenFlags,
8        types::{FromSql, ToSql, Type as RusqliteType},
9        version_number,
10    },
11    semver::Version,
12};
13use cfg_if::cfg_if;
14use std::{fmt::Display, sync::LazyLock};
15
16/// Binary Large Object (BLOB), represented as a [`Vec<u8>`](std::vec::Vec).
17pub type Blob = Vec<u8>;
18
19/// Flags to be used for [`Connection::open_with_flags()`](rusqlite::Connection::open_with_flags).
20pub static OPEN_FLAGS: LazyLock<OpenFlags> = LazyLock::new(|| {
21    let mut flags = OpenFlags::default();
22    flags.remove(OpenFlags::SQLITE_OPEN_URI);
23    flags
24});
25
26// ---------------------------------------------------------------------------
27// Sealed Type
28// ---------------------------------------------------------------------------
29
30trait Sealed {}
31
32macro_rules! impl_sealed {
33    ($($t:ty),*) => {
34        $(
35            impl Sealed for $t {}
36        )*
37    };
38}
39
40impl_sealed!(i32, u32, i64, String, Blob);
41
42// ---------------------------------------------------------------------------
43// SQL Type Holder
44// ---------------------------------------------------------------------------
45
46#[derive(Eq, PartialEq, Copy, Clone, Debug)]
47pub struct SqlType(pub RusqliteType);
48
49impl SqlType {
50    #[inline(always)]
51    pub const fn new(value: RusqliteType) -> Self {
52        Self(value)
53    }
54}
55
56impl Ord for SqlType {
57    #[inline]
58    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
59        (self.0 as usize).cmp(&(other.0 as usize))
60    }
61}
62
63impl PartialOrd for SqlType {
64    #[inline]
65    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
66        Some(self.cmp(other))
67    }
68}
69
70impl Display for SqlType {
71    #[inline]
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        f.write_str(&self.0.to_string().to_ascii_uppercase())
74    }
75}
76
77pub const SQL_TYPE_TEXT: SqlType = SqlType::new(RusqliteType::Text);
78pub static SQL_TYPES: [SqlType; 3usize] = [SqlType::new(RusqliteType::Integer), SqlType::new(RusqliteType::Text), SqlType::new(RusqliteType::Blob)];
79
80// ---------------------------------------------------------------------------
81// Key or Value Type
82// ---------------------------------------------------------------------------
83
84/// The type of the keys or values that are stored in an [`SQLiteMap`](crate::SQLiteMap) or an [`SQLiteSet`](crate::SQLiteSet).
85///
86/// Currently, the following types are supported:
87///
88/// Type                              | SQLite storage datatype
89/// --------------------------------- | -----------------------
90/// **`i32`**                         | `INTEGER`
91/// **`u32`**                         | `INTEGER`
92/// **`i64`**                         | `INTEGER`
93/// **`Vec<u8>`**, alias **[`Blob`]** | `BLOB`
94/// **[`String`]**                    | `TEXT`
95///
96/// &nbsp;
97///
98/// <div class="warning">
99///
100/// 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`.
101///
102/// </div>
103#[allow(private_bounds)]
104pub trait Type: FromSql + ToSql + Sealed {
105    /// The SQLite storage datatype that corresponds to this type.
106    const SQL_TYPE: SqlType;
107}
108
109macro_rules! impl_type {
110    ($type:ty, $sql_type:ident) => {
111        impl Type for $type {
112            const SQL_TYPE: SqlType = SqlType::new(RusqliteType::$sql_type);
113        }
114    };
115}
116
117impl_type!(i32, Integer);
118impl_type!(u32, Integer);
119impl_type!(i64, Integer);
120impl_type!(Blob, Blob);
121impl_type!(String, Text);
122
123// ---------------------------------------------------------------------------
124// Character-based Types
125// ---------------------------------------------------------------------------
126
127/// Represents a character-based type, e.g., [`String`].
128///
129/// Keys of these types can be treated case-sensitive (default) or case-insensitive.
130pub trait CharType {}
131
132macro_rules! impl_chartype {
133    ($($t:ty),*) => {
134        $(
135            impl CharType for $t {}
136        )*
137    };
138}
139
140impl_chartype!(String);
141
142// ---------------------------------------------------------------------------
143// Size Type
144// ---------------------------------------------------------------------------
145
146cfg_if! {
147    if #[cfg(target_pointer_width = "64")] {
148        /// The "size" type used by [`len()`](crate::SQLiteSet::len), equal to `usize` on this platform.
149        pub type Size = usize;
150    } else {
151        /// The "size" type used by [`len()`](crate::SQLiteSet::len), equal to `u64` on this platform.
152        pub type Size = u64;
153    }
154}
155
156/// Provides useful helper functions for the [`Size`](crate::Size) type.
157pub trait SizeExt {
158    fn from_i64(value: i64) -> Self;
159    fn from_u64(value: u64) -> Self;
160    fn to_u64(self) -> u64;
161    fn to_usize(self) -> Option<usize>;
162}
163
164impl SizeExt for Size {
165    #[inline(always)]
166    fn from_i64(value: i64) -> Self {
167        value.try_into().unwrap_or_default()
168    }
169
170    #[inline(always)]
171    fn from_u64(value: u64) -> Self {
172        value as Self
173    }
174
175    #[allow(clippy::unnecessary_cast)]
176    #[inline(always)]
177    fn to_u64(self) -> u64 {
178        self as u64
179    }
180
181    #[inline(always)]
182    fn to_usize(self) -> Option<usize> {
183        cfg_if! {
184            if #[cfg(target_pointer_width = "64")] {
185                Some(self)
186            } else {
187                self.try_into().ok()
188            }
189        }
190    }
191}
192
193// ---------------------------------------------------------------------------
194// SQLite Error
195// ---------------------------------------------------------------------------
196
197/// Indicates that the operation failed due to an SQLite error.
198///
199/// 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.
200///
201/// Wraps the underlying [`RusqliteError`](crate::rusqlite::Error).
202#[allow(dead_code)]
203#[derive(Debug)]
204pub struct Error(RusqliteError);
205
206impl Error {
207    pub fn into_inner(self) -> RusqliteError {
208        self.0
209    }
210}
211
212impl From<RusqliteError> for Error {
213    #[inline]
214    fn from(error: RusqliteError) -> Self {
215        Self(error)
216    }
217}
218
219#[inline]
220pub fn check_constraint_violation(error: RusqliteError) -> Result<bool, Error> {
221    match error {
222        RusqliteError::SqliteFailure(sql_error, _) => match sql_error.code {
223            ErrorCode::ConstraintViolation => Ok(false),
224            _ => Err(error.into()),
225        },
226        _ => Err(error.into()),
227    }
228}
229
230/// Returns the package version of the `sqlite-containers` package.
231pub fn version() -> &'static Version {
232    static PKG_VERSION: LazyLock<Version> = LazyLock::new(|| Version::parse(env!["CARGO_PKG_VERSION"]).expect("Failed to parse version!"));
233    &PKG_VERSION
234}
235
236/// Returns the version of the underlying native SQLite library.
237pub fn sqlite_version() -> &'static Version {
238    static SQLITE_VERSION: LazyLock<Version> = LazyLock::new(|| {
239        let version = version_number() as u64;
240        Version::new(version / 1000000u64, (version % 1000000u64) / 1000u64, version % 1000u64)
241    });
242    &SQLITE_VERSION
243}
244
245// ---------------------------------------------------------------------------
246// Tests
247// ---------------------------------------------------------------------------
248
249#[cfg(test)]
250mod test {
251    use super::*;
252    use crate::rusqlite::ffi::{Error as FfiError, ErrorCode as FfiErrorCode, SQLITE_CONSTRAINT, SQLITE_NOMEM};
253    use std::path::PathBuf;
254
255    #[cfg(target_pointer_width = "64")]
256    #[test]
257    fn test_size_p64() {
258        assert_eq!(Size::from_i64(i64::MAX), i64::MAX as usize);
259        assert_eq!(Size::from_i64(1i64), 1usize);
260        assert_eq!(Size::from_i64(0i64), usize::MIN);
261        assert_eq!(Size::from_i64(-1i64), usize::MIN);
262        assert_eq!(Size::from_i64(i64::MIN), usize::MIN);
263
264        assert_eq!(Size::from_u64(u64::MAX), usize::MAX);
265        assert_eq!(Size::from_u64(1u64), 1usize);
266        assert_eq!(Size::from_u64(u64::MIN), usize::MIN);
267
268        assert_eq!(Size::from_i64(i64::MAX).to_u64(), i64::MAX as u64);
269        assert_eq!(Size::from_i64(i64::MAX).to_usize(), Some(i64::MAX as usize));
270    }
271
272    #[cfg(not(target_pointer_width = "64"))]
273    #[test]
274    fn test_size_p32() {
275        assert_eq!(Size::from_i64(i64::MAX), i64::MAX as u64);
276        assert_eq!(Size::from_i64(1i64), 1u64);
277        assert_eq!(Size::from_i64(0i64), u64::MIN);
278        assert_eq!(Size::from_i64(-1i64), u64::MIN);
279        assert_eq!(Size::from_i64(i64::MIN), u64::MIN);
280
281        assert_eq!(Size::from_u64(u64::MAX), u64::MAX);
282        assert_eq!(Size::from_u64(1u64), 1u64);
283        assert_eq!(Size::from_u64(u64::MIN), u64::MIN);
284
285        assert_eq!(Size::from_i64(i64::MAX).to_u64(), i64::MAX as u64);
286        assert_eq!(Size::from_i64(usize::MAX as i64).to_usize(), Some(usize::MAX));
287        assert!(Size::from_i64(i64::MAX).to_usize().is_none());
288    }
289
290    #[test]
291    fn test_sql_type() {
292        const ALL_TYPES: [RusqliteType; 3usize] = [RusqliteType::Integer, RusqliteType::Blob, RusqliteType::Text];
293        for native_type in ALL_TYPES {
294            let sql_type = SqlType::new(native_type);
295            assert_eq!(sql_type.0, native_type);
296            for other_type in ALL_TYPES {
297                let other_type = SqlType::new(other_type);
298                assert_eq!(sql_type.partial_cmp(&other_type).unwrap(), sql_type.cmp(&other_type));
299            }
300        }
301    }
302
303    #[test]
304    fn test_error() {
305        let error_1 = Error::from(RusqliteError::InvalidQuery);
306        assert_eq!(error_1.into_inner(), RusqliteError::InvalidQuery);
307
308        let error_2 = Error::from(RusqliteError::InvalidPath(PathBuf::from("filename.txt")));
309        assert!(matches!(error_2.into_inner(), RusqliteError::InvalidPath(_)));
310
311        let error_3 = Error::from(RusqliteError::SqliteFailure(FfiError::new(SQLITE_NOMEM), Some("Memory allocation fail!".to_owned())));
312        assert!(matches!(error_3.into_inner(), RusqliteError::SqliteFailure(FfiError { code: FfiErrorCode::OutOfMemory, .. }, Some(_))));
313    }
314
315    #[test]
316    fn test_check_constraint_violation() {
317        let error_1 = check_constraint_violation(RusqliteError::InvalidQuery);
318        assert!(matches!(error_1, Err(Error(..))));
319
320        let error_2 = check_constraint_violation(RusqliteError::SqliteFailure(FfiError::new(SQLITE_CONSTRAINT), None));
321        assert!(matches!(error_2, Ok(false)));
322
323        let error_3 = check_constraint_violation(RusqliteError::SqliteFailure(FfiError::new(SQLITE_NOMEM), None));
324        assert!(matches!(error_3, Err(Error(..))));
325    }
326
327    #[test]
328    fn test_get_version() {
329        let (pkg_version, sqlite_version) = (version(), sqlite_version());
330        eprintln!("pkg_version: {pkg_version:?}");
331        eprintln!("sqlite_version: {sqlite_version:?}");
332
333        assert_eq!(pkg_version.to_string(), env!["CARGO_PKG_VERSION"]);
334        assert!(
335            (sqlite_version.major > 3)
336                || ((sqlite_version.major == 3) && (sqlite_version.minor > 34))
337                || ((sqlite_version.major == 3) && (sqlite_version.minor == 34) && (sqlite_version.minor >= 1))
338        );
339    }
340}