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, OpenFlags,
8        ffi::{Error as FfiError, ErrorCode as FfiErrorCode},
9        types::{FromSql, ToSql, Type as RusqliteType},
10        version_number,
11    },
12    semver::Version,
13};
14use cfg_if::cfg_if;
15use std::{
16    fmt::{Debug, Display},
17    sync::LazyLock,
18};
19
20#[cfg(any(feature = "backup", feature = "checked"))]
21use crate::rusqlite::Connection;
22
23// ---------------------------------------------------------------------------
24// Flags
25// ---------------------------------------------------------------------------
26
27/// Binary Large Object (BLOB), represented as a [`Vec<u8>`](std::vec::Vec).
28pub type Blob = Vec<u8>;
29
30/// Flags to be used for [`Connection::open_with_flags()`](rusqlite::Connection::open_with_flags).
31pub static OPEN_FLAGS: LazyLock<OpenFlags> = LazyLock::new(|| {
32    let mut flags = OpenFlags::default();
33    flags.remove(OpenFlags::SQLITE_OPEN_URI);
34    flags
35});
36
37/// Flags to be used for [`Connection::open_with_flags()`](rusqlite::Connection::open_with_flags) *without* `SQLITE_OPEN_CREATE`.
38pub static OPEN_FLAGS_NOCREATE: LazyLock<OpenFlags> = LazyLock::new(|| {
39    let mut flags = OpenFlags::default();
40    flags.remove(OpenFlags::SQLITE_OPEN_URI);
41    flags.remove(OpenFlags::SQLITE_OPEN_CREATE);
42    flags
43});
44
45/// Flags to be used for [`Connection::open_with_flags()`](rusqlite::Connection::open_with_flags) *without* `SQLITE_OPEN_CREATE`.
46pub static OPEN_FLAGS_RDONLY: LazyLock<OpenFlags> = LazyLock::new(|| {
47    let mut flags = OpenFlags::default();
48    flags.remove(OpenFlags::SQLITE_OPEN_URI);
49    flags.remove(OpenFlags::SQLITE_OPEN_CREATE);
50    flags.remove(OpenFlags::SQLITE_OPEN_READ_WRITE);
51    flags.insert(OpenFlags::SQLITE_OPEN_READ_ONLY);
52    flags
53});
54
55// ---------------------------------------------------------------------------
56// Sealed Type
57// ---------------------------------------------------------------------------
58
59trait Sealed {}
60
61macro_rules! impl_sealed {
62    ($($t:ty),*) => {
63        $(
64            impl Sealed for $t {}
65        )*
66    };
67}
68
69impl_sealed!(i32, u32, i64, String, Blob);
70
71// ---------------------------------------------------------------------------
72// SQL Type Holder
73// ---------------------------------------------------------------------------
74
75#[derive(Eq, PartialEq, Copy, Clone, Debug)]
76pub struct SqlType(pub RusqliteType);
77
78impl SqlType {
79    #[inline(always)]
80    pub const fn new(value: RusqliteType) -> Self {
81        Self(value)
82    }
83}
84
85impl Ord for SqlType {
86    #[inline]
87    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
88        (self.0 as usize).cmp(&(other.0 as usize))
89    }
90}
91
92impl PartialOrd for SqlType {
93    #[inline]
94    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
95        Some(self.cmp(other))
96    }
97}
98
99impl Display for SqlType {
100    #[inline]
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        f.write_str(&self.0.to_string().to_ascii_uppercase())
103    }
104}
105
106pub const SQL_TYPE_TEXT: SqlType = SqlType::new(RusqliteType::Text);
107pub static SQL_TYPES: [SqlType; 3usize] = [SqlType::new(RusqliteType::Integer), SqlType::new(RusqliteType::Text), SqlType::new(RusqliteType::Blob)];
108
109// ---------------------------------------------------------------------------
110// Key or Value Type
111// ---------------------------------------------------------------------------
112
113/// The type of the keys or values that are stored in an [`SQLiteMap`](crate::SQLiteMap) or an [`SQLiteSet`](crate::SQLiteSet).
114///
115/// Currently, the following types are supported:
116///
117/// Type                              | SQLite storage datatype
118/// --------------------------------- | -----------------------
119/// **`i32`**                         | `INTEGER`
120/// **`u32`**                         | `INTEGER`
121/// **`i64`**                         | `INTEGER`
122/// **`Vec<u8>`**, alias **[`Blob`]** | `BLOB`
123/// **[`String`]**                    | `TEXT`
124///
125/// &nbsp;
126///
127/// <div class="warning">
128///
129/// 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`.
130///
131/// </div>
132#[allow(private_bounds)]
133pub trait Type: FromSql + ToSql + Sealed {
134    /// The SQLite storage datatype that corresponds to this type.
135    const SQL_TYPE: SqlType;
136}
137
138macro_rules! impl_type {
139    ($type:ty, $sql_type:ident) => {
140        impl Type for $type {
141            const SQL_TYPE: SqlType = SqlType::new(RusqliteType::$sql_type);
142        }
143    };
144}
145
146impl_type!(i32, Integer);
147impl_type!(u32, Integer);
148impl_type!(i64, Integer);
149impl_type!(Blob, Blob);
150impl_type!(String, Text);
151
152// ---------------------------------------------------------------------------
153// Character-based Types
154// ---------------------------------------------------------------------------
155
156/// Represents a character-based type, e.g., [`String`].
157///
158/// Keys of these types can be treated case-sensitive (default) or case-insensitive.
159pub trait CharType {}
160
161macro_rules! impl_chartype {
162    ($($t:ty),*) => {
163        $(
164            impl CharType for $t {}
165        )*
166    };
167}
168
169impl_chartype!(String);
170
171// ---------------------------------------------------------------------------
172// Size Type
173// ---------------------------------------------------------------------------
174
175cfg_if! {
176    if #[cfg(target_pointer_width = "64")] {
177        /// The "size" type used by [`len()`](crate::SQLiteSet::len), equal to `usize` on this platform.
178        pub type Size = usize;
179    } else {
180        /// The "size" type used by [`len()`](crate::SQLiteSet::len), equal to `u64` on this platform.
181        pub type Size = u64;
182    }
183}
184
185/// Provides useful helper functions for the [`Size`](crate::Size) type.
186pub trait SizeExt {
187    /// Create a new [`Size`] instance from an `i64` value.
188    fn from_i64(value: i64) -> Self;
189    /// Create a new [`Size`] instance from an `u64` value.
190    fn from_u64(value: u64) -> Self;
191    /// Convert this [`Size`] instance to an `u64` value.
192    fn to_u64(self) -> u64;
193    /// Convert this [`Size`] instance to an `usize` value, if possible.
194    fn to_usize(self) -> Option<usize>;
195}
196
197impl SizeExt for Size {
198    #[inline(always)]
199    fn from_i64(value: i64) -> Self {
200        value.try_into().unwrap_or_default()
201    }
202
203    #[inline(always)]
204    fn from_u64(value: u64) -> Self {
205        value as Self
206    }
207
208    #[allow(clippy::unnecessary_cast)]
209    #[inline(always)]
210    fn to_u64(self) -> u64 {
211        self as u64
212    }
213
214    #[inline(always)]
215    fn to_usize(self) -> Option<usize> {
216        cfg_if! {
217            if #[cfg(target_pointer_width = "64")] {
218                Some(self)
219            } else {
220                self.try_into().ok()
221            }
222        }
223    }
224}
225
226// ---------------------------------------------------------------------------
227// SQLite Error
228// ---------------------------------------------------------------------------
229
230/// Error type that indicates that a container operation has failed.
231///
232/// 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.
233#[allow(dead_code)]
234#[derive(Debug, PartialEq)]
235pub enum Error {
236    /// Indicates that the operation failed because of an SQLite error; wraps the underlying [`RusqliteError`](crate::rusqlite::Error).
237    SqlError(RusqliteError),
238    /// An attempt was made to open an existing container (database), but the existing database schema is incompatible.
239    InvalidSchema,
240    /// The [integrity check](https://www.sqlite.org/pragma.html#pragma_integrity_check) for the *file-based* container (database) file has failed. Take care!
241    IntegrityCheck,
242    /// An attempt was made to use a feature that is **not** supported by this build of `sqlite-containers`.
243    Unsupported,
244}
245
246impl Error {
247    /// Returns the wrapped [`RusqliteError`](crate::rusqlite::Error), if this `Error` originates from SQLite; otherwise `None` is returned.
248    pub fn into_inner(self) -> Option<RusqliteError> {
249        match self {
250            Error::SqlError(inner) => Some(inner),
251            _ => None,
252        }
253    }
254}
255
256impl From<RusqliteError> for Error {
257    #[inline]
258    fn from(inner: RusqliteError) -> Self {
259        Self::SqlError(inner)
260    }
261}
262
263// ---------------------------------------------------------------------------
264// TryClone Trait
265// ---------------------------------------------------------------------------
266
267/// This is the *failable* version of the standard [`Clone`](std::clone::Clone) trait.
268pub trait TryClone: Sized {
269    /// The error type to be returned by [`try_clone()`](Self::try_clone).
270    type Error: Debug;
271    /// This is the *failable* version of the standard [`clone()`](std::clone::Clone::clone) function.
272    fn try_clone(&self) -> Result<Self, Self::Error>;
273}
274
275cfg_if! {
276    if #[cfg(feature = "backup")] {
277        use crate::rusqlite::backup::Backup;
278        use std::time::Duration;
279
280        pub(crate) fn backup_db(src: &Connection, dst: &mut Connection) -> Result<(), Error> {
281            let backup = Backup::new(src, dst)?;
282            Ok(backup.run_to_completion(1000i32, Duration::ZERO, None)?)
283        }
284    }
285}
286
287// ---------------------------------------------------------------------------
288// Helper Functions
289// ---------------------------------------------------------------------------
290
291#[inline(always)]
292pub(crate) fn check_constraint_violation(error: RusqliteError) -> Result<bool, Error> {
293    match error {
294        RusqliteError::SqliteFailure(FfiError { code: FfiErrorCode::ConstraintViolation, .. }, _) => Ok(false),
295        _ => Err(error.into()),
296    }
297}
298
299#[inline(always)]
300fn trim_schema_str(sql: &str) -> &str {
301    const PREFIX: (&str, &str) = ("CREATE TABLE", "IF NOT EXISTS");
302    let sql = sql.trim_ascii();
303    let sql = sql.strip_prefix(PREFIX.0).map(str::trim_ascii_start).unwrap_or(sql);
304    let sql = sql.strip_prefix(PREFIX.1).map(str::trim_ascii_start).unwrap_or(sql);
305    sql.strip_suffix(';').map(str::trim_ascii_end).unwrap_or(sql)
306}
307
308#[inline(always)]
309pub(crate) fn verify_schema(sql_create: &str, sql_schema: &str) -> bool {
310    trim_schema_str(sql_create).eq(trim_schema_str(sql_schema))
311}
312
313cfg_if! {
314    if #[cfg(feature = "checked")] {
315        #[inline(always)]
316        fn map_check_result(check_result: Option<String>) -> Result<(), Error> {
317            match check_result {
318                Some(value) if value.eq_ignore_ascii_case("ok") => Ok(()),
319                _ => Err(Error::IntegrityCheck),
320            }
321        }
322
323        #[inline(always)]
324        pub(crate) fn integrity_check(connection: &Connection) -> Result<(), Error> {
325            use crate::rusqlite::OptionalExtension;
326            const SQL_CHECK_DATA: &str = "PRAGMA integrity_check;";
327            map_check_result(connection.query_row(SQL_CHECK_DATA, [], |row| row.get::<_, String>(0)).optional()?)
328        }
329    }
330}
331
332// ---------------------------------------------------------------------------
333// Version Getters
334// ---------------------------------------------------------------------------
335
336/// Returns the package version of the `sqlite-containers` package.
337pub fn version() -> &'static Version {
338    static PKG_VERSION: LazyLock<Version> = LazyLock::new(|| Version::parse(env!["CARGO_PKG_VERSION"]).expect("Failed to parse version!"));
339    &PKG_VERSION
340}
341
342/// Returns the version of the underlying native SQLite library.
343pub fn sqlite_version() -> &'static Version {
344    static SQLITE_VERSION: LazyLock<Version> = LazyLock::new(|| {
345        let version = version_number() as u64;
346        Version::new(version / 1000000u64, (version % 1000000u64) / 1000u64, version % 1000u64)
347    });
348    &SQLITE_VERSION
349}
350
351// ---------------------------------------------------------------------------
352// Tests
353// ---------------------------------------------------------------------------
354
355#[cfg(test)]
356mod test {
357    use super::*;
358    use crate::rusqlite::ffi::{Error as FfiError, ErrorCode as FfiErrorCode, SQLITE_CONSTRAINT, SQLITE_NOMEM};
359    use std::path::PathBuf;
360
361    #[cfg(target_pointer_width = "64")]
362    #[test]
363    fn test_size_p64() {
364        assert_eq!(Size::from_i64(i64::MAX), i64::MAX as usize);
365        assert_eq!(Size::from_i64(1i64), 1usize);
366        assert_eq!(Size::from_i64(0i64), usize::MIN);
367        assert_eq!(Size::from_i64(-1i64), usize::MIN);
368        assert_eq!(Size::from_i64(i64::MIN), usize::MIN);
369
370        assert_eq!(Size::from_u64(u64::MAX), usize::MAX);
371        assert_eq!(Size::from_u64(1u64), 1usize);
372        assert_eq!(Size::from_u64(u64::MIN), usize::MIN);
373
374        assert_eq!(Size::from_i64(i64::MAX).to_u64(), i64::MAX as u64);
375        assert_eq!(Size::from_i64(i64::MAX).to_usize(), Some(i64::MAX as usize));
376    }
377
378    #[cfg(not(target_pointer_width = "64"))]
379    #[test]
380    fn test_size_p32() {
381        assert_eq!(Size::from_i64(i64::MAX), i64::MAX as u64);
382        assert_eq!(Size::from_i64(1i64), 1u64);
383        assert_eq!(Size::from_i64(0i64), u64::MIN);
384        assert_eq!(Size::from_i64(-1i64), u64::MIN);
385        assert_eq!(Size::from_i64(i64::MIN), u64::MIN);
386
387        assert_eq!(Size::from_u64(u64::MAX), u64::MAX);
388        assert_eq!(Size::from_u64(1u64), 1u64);
389        assert_eq!(Size::from_u64(u64::MIN), u64::MIN);
390
391        assert_eq!(Size::from_i64(i64::MAX).to_u64(), i64::MAX as u64);
392        assert_eq!(Size::from_i64(usize::MAX as i64).to_usize(), Some(usize::MAX));
393        assert!(Size::from_i64(i64::MAX).to_usize().is_none());
394    }
395
396    #[test]
397    fn test_sql_type() {
398        const ALL_TYPES: [RusqliteType; 3usize] = [RusqliteType::Integer, RusqliteType::Blob, RusqliteType::Text];
399        for native_type in ALL_TYPES {
400            let sql_type = SqlType::new(native_type);
401            assert_eq!(sql_type.0, native_type);
402            for other_type in ALL_TYPES {
403                let other_type = SqlType::new(other_type);
404                assert_eq!(sql_type.partial_cmp(&other_type).unwrap(), sql_type.cmp(&other_type));
405            }
406        }
407    }
408
409    #[test]
410    fn test_error() {
411        let error_1 = Error::from(RusqliteError::InvalidQuery);
412        assert!(matches!(error_1.into_inner(), Some(RusqliteError::InvalidQuery)));
413
414        let error_2 = Error::from(RusqliteError::InvalidPath(PathBuf::from("filename.txt")));
415        assert!(matches!(error_2.into_inner(), Some(RusqliteError::InvalidPath(_))));
416
417        let error_3 = Error::from(RusqliteError::SqliteFailure(FfiError::new(SQLITE_NOMEM), Some("Memory allocation fail!".to_owned())));
418        assert!(matches!(error_3.into_inner(), Some(RusqliteError::SqliteFailure(FfiError { code: FfiErrorCode::OutOfMemory, .. }, Some(_)))));
419
420        let error_4 = Error::InvalidSchema;
421        assert!(error_4.into_inner().is_none());
422    }
423
424    #[test]
425    fn test_check_constraint_violation() {
426        let error_1 = check_constraint_violation(RusqliteError::InvalidQuery);
427        assert!(matches!(error_1, Err(Error::SqlError(..))));
428
429        let error_2 = check_constraint_violation(RusqliteError::SqliteFailure(FfiError::new(SQLITE_CONSTRAINT), None));
430        assert!(matches!(error_2, Ok(false)));
431
432        let error_3 = check_constraint_violation(RusqliteError::SqliteFailure(FfiError::new(SQLITE_NOMEM), None));
433        assert!(matches!(error_3, Err(Error::SqlError(..))));
434    }
435
436    #[test]
437    fn test_integrity_check_result() {
438        assert_eq!(map_check_result(None), Err(Error::IntegrityCheck));
439        assert_eq!(map_check_result(Some("xy".into())), Err(Error::IntegrityCheck));
440        assert_eq!(map_check_result(Some("ok".into())), Ok(()));
441    }
442
443    #[test]
444    fn test_get_version() {
445        let (pkg_version, sqlite_version) = (version(), sqlite_version());
446
447        eprintln!("pkg_version: {pkg_version:?}");
448        eprintln!("sqlite_version: {sqlite_version:?}");
449
450        assert_eq!(pkg_version.to_string(), env!["CARGO_PKG_VERSION"]);
451        assert!(version_to_u64(sqlite_version) > 0x3_000022_000000u64);
452    }
453
454    fn version_to_u64(v: &Version) -> u64 {
455        assert!(v.major <= 0x00FFFF);
456        assert!(v.minor <= 0xFFFFFF);
457        assert!(v.patch <= 0xFFFFFF);
458        (v.major << 48) | (v.minor << 24) | v.patch
459    }
460}