1use 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
23pub type Blob = Vec<u8>;
29
30pub static OPEN_FLAGS: LazyLock<OpenFlags> = LazyLock::new(|| {
32 let mut flags = OpenFlags::default();
33 flags.remove(OpenFlags::SQLITE_OPEN_URI);
34 flags
35});
36
37pub 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
45pub 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
55trait 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#[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#[allow(private_bounds)]
133pub trait Type: FromSql + ToSql + Sealed {
134 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
152pub trait CharType {}
160
161macro_rules! impl_chartype {
162 ($($t:ty),*) => {
163 $(
164 impl CharType for $t {}
165 )*
166 };
167}
168
169impl_chartype!(String);
170
171cfg_if! {
176 if #[cfg(target_pointer_width = "64")] {
177 pub type Size = usize;
179 } else {
180 pub type Size = u64;
182 }
183}
184
185pub trait SizeExt {
187 fn from_i64(value: i64) -> Self;
189 fn from_u64(value: u64) -> Self;
191 fn to_u64(self) -> u64;
193 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#[allow(dead_code)]
234#[derive(Debug, PartialEq)]
235pub enum Error {
236 SqlError(RusqliteError),
238 InvalidSchema,
240 IntegrityCheck,
242 Unsupported,
244}
245
246impl Error {
247 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
263pub trait TryClone: Sized {
269 type Error: Debug;
271 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#[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
332pub 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
342pub 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#[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}