Skip to main content

ic_sqlite_vfs/db/
row.rs

1//! Typed reads from the current SQLite result row.
2//!
3//! `Row` is only valid until the owning statement is stepped, reset, or
4//! finalized. Getters copy text and blob data before returning.
5
6use crate::db::DbError;
7use crate::sqlite_vfs::ffi;
8use std::ffi::c_int;
9use std::marker::PhantomData;
10use std::slice;
11
12pub struct Row<'row> {
13    raw: *mut ffi::sqlite3_stmt,
14    _row: PhantomData<&'row ()>,
15}
16
17pub trait FromColumn: Sized {
18    const EXPECTED: &'static str;
19
20    fn read(row: &Row<'_>, index: usize) -> Result<Self, DbError>;
21}
22
23impl Row<'_> {
24    pub(crate) fn new(raw: *mut ffi::sqlite3_stmt) -> Self {
25        Self {
26            raw,
27            _row: PhantomData,
28        }
29    }
30
31    pub fn get<T: FromColumn>(&self, index: usize) -> Result<T, DbError> {
32        self.check_index(index)?;
33        T::read(self, index)
34    }
35
36    fn check_index(&self, index: usize) -> Result<(), DbError> {
37        let count = unsafe { ffi::sqlite3_column_count(self.raw) };
38        let count = usize::try_from(count).unwrap_or(0);
39        if index < count {
40            Ok(())
41        } else {
42            Err(DbError::ColumnOutOfRange { index, count })
43        }
44    }
45
46    fn column_type(&self, index: usize) -> Result<c_int, DbError> {
47        let index = c_int::try_from(index).map_err(|_| DbError::ColumnOutOfRange {
48            index,
49            count: usize::try_from(unsafe { ffi::sqlite3_column_count(self.raw) }).unwrap_or(0),
50        })?;
51        Ok(unsafe { ffi::sqlite3_column_type(self.raw, index) })
52    }
53
54    fn require_type(
55        &self,
56        index: usize,
57        expected: &'static str,
58        actual: c_int,
59    ) -> Result<(), DbError> {
60        let expected_code = match expected {
61            "TEXT" => ffi::SQLITE_TEXT,
62            "INTEGER" => ffi::SQLITE_INTEGER,
63            "REAL" => ffi::SQLITE_FLOAT,
64            "BLOB" => ffi::SQLITE_BLOB,
65            "NULL" => ffi::SQLITE_NULL,
66            _ => actual,
67        };
68        if actual == expected_code {
69            Ok(())
70        } else {
71            Err(DbError::TypeMismatch {
72                index,
73                expected,
74                actual: type_name(actual),
75            })
76        }
77    }
78}
79
80impl FromColumn for String {
81    const EXPECTED: &'static str = "TEXT";
82
83    fn read(row: &Row<'_>, index: usize) -> Result<Self, DbError> {
84        let actual = row.column_type(index)?;
85        row.require_type(index, Self::EXPECTED, actual)?;
86        let index = c_int::try_from(index).map_err(|_| DbError::TooManyParameters)?;
87        let text = unsafe { ffi::sqlite3_column_text(row.raw, index) };
88        let len = unsafe { ffi::sqlite3_column_bytes(row.raw, index) };
89        let len = usize::try_from(len).map_err(|_| DbError::TextTooLarge)?;
90        if len == 0 {
91            return Ok(String::new());
92        }
93        if text.is_null() {
94            return Ok(String::new());
95        }
96        let bytes = unsafe { slice::from_raw_parts(text.cast::<u8>(), len) };
97        Ok(String::from_utf8_lossy(bytes).into_owned())
98    }
99}
100
101impl FromColumn for i64 {
102    const EXPECTED: &'static str = "INTEGER";
103
104    fn read(row: &Row<'_>, index: usize) -> Result<Self, DbError> {
105        let actual = row.column_type(index)?;
106        row.require_type(index, Self::EXPECTED, actual)?;
107        let index = c_int::try_from(index).map_err(|_| DbError::TooManyParameters)?;
108        Ok(unsafe { ffi::sqlite3_column_int64(row.raw, index) })
109    }
110}
111
112impl FromColumn for f64 {
113    const EXPECTED: &'static str = "REAL";
114
115    fn read(row: &Row<'_>, index: usize) -> Result<Self, DbError> {
116        let actual = row.column_type(index)?;
117        row.require_type(index, Self::EXPECTED, actual)?;
118        let index = c_int::try_from(index).map_err(|_| DbError::TooManyParameters)?;
119        Ok(unsafe { ffi::sqlite3_column_double(row.raw, index) })
120    }
121}
122
123impl FromColumn for Vec<u8> {
124    const EXPECTED: &'static str = "BLOB";
125
126    fn read(row: &Row<'_>, index: usize) -> Result<Self, DbError> {
127        let actual = row.column_type(index)?;
128        row.require_type(index, Self::EXPECTED, actual)?;
129        let index = c_int::try_from(index).map_err(|_| DbError::TooManyParameters)?;
130        let ptr = unsafe { ffi::sqlite3_column_blob(row.raw, index) };
131        let len = unsafe { ffi::sqlite3_column_bytes(row.raw, index) };
132        let len = usize::try_from(len).map_err(|_| DbError::BlobTooLarge)?;
133        if len == 0 {
134            return Ok(Vec::new());
135        }
136        if ptr.is_null() {
137            return Ok(Vec::new());
138        }
139        let bytes = unsafe { slice::from_raw_parts(ptr.cast::<u8>(), len) };
140        Ok(bytes.to_vec())
141    }
142}
143
144impl<T: FromColumn> FromColumn for Option<T> {
145    const EXPECTED: &'static str = T::EXPECTED;
146
147    fn read(row: &Row<'_>, index: usize) -> Result<Self, DbError> {
148        if row.column_type(index)? == ffi::SQLITE_NULL {
149            Ok(None)
150        } else {
151            T::read(row, index).map(Some)
152        }
153    }
154}
155
156fn type_name(code: c_int) -> &'static str {
157    match code {
158        ffi::SQLITE_INTEGER => "INTEGER",
159        ffi::SQLITE_FLOAT => "REAL",
160        ffi::SQLITE_TEXT => "TEXT",
161        ffi::SQLITE_BLOB => "BLOB",
162        ffi::SQLITE_NULL => "NULL",
163        _ => "UNKNOWN",
164    }
165}