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
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub struct TextLen(pub usize);
25
26impl Row<'_> {
27    pub(crate) fn new(raw: *mut ffi::sqlite3_stmt) -> Self {
28        Self {
29            raw,
30            _row: PhantomData,
31        }
32    }
33
34    pub fn get<T: FromColumn>(&self, index: usize) -> Result<T, DbError> {
35        self.check_index(index)?;
36        T::read(self, index)
37    }
38
39    fn check_index(&self, index: usize) -> Result<(), DbError> {
40        let count = unsafe { ffi::sqlite3_column_count(self.raw) };
41        let count = usize::try_from(count).unwrap_or(0);
42        if index < count {
43            Ok(())
44        } else {
45            Err(DbError::ColumnOutOfRange { index, count })
46        }
47    }
48
49    fn column_type(&self, index: usize) -> Result<c_int, DbError> {
50        let index = c_int::try_from(index).map_err(|_| DbError::ColumnOutOfRange {
51            index,
52            count: usize::try_from(unsafe { ffi::sqlite3_column_count(self.raw) }).unwrap_or(0),
53        })?;
54        Ok(unsafe { ffi::sqlite3_column_type(self.raw, index) })
55    }
56
57    fn require_type(
58        &self,
59        index: usize,
60        expected: &'static str,
61        actual: c_int,
62    ) -> Result<(), DbError> {
63        let expected_code = match expected {
64            "TEXT" => ffi::SQLITE_TEXT,
65            "INTEGER" => ffi::SQLITE_INTEGER,
66            "REAL" => ffi::SQLITE_FLOAT,
67            "BLOB" => ffi::SQLITE_BLOB,
68            "NULL" => ffi::SQLITE_NULL,
69            _ => actual,
70        };
71        if actual == expected_code {
72            Ok(())
73        } else {
74            Err(DbError::TypeMismatch {
75                index,
76                expected,
77                actual: type_name(actual),
78            })
79        }
80    }
81}
82
83impl FromColumn for String {
84    const EXPECTED: &'static str = "TEXT";
85
86    fn read(row: &Row<'_>, index: usize) -> Result<Self, DbError> {
87        let actual = row.column_type(index)?;
88        row.require_type(index, Self::EXPECTED, actual)?;
89        let index = c_int::try_from(index).map_err(|_| DbError::TooManyParameters)?;
90        let text = unsafe { ffi::sqlite3_column_text(row.raw, index) };
91        let len = unsafe { ffi::sqlite3_column_bytes(row.raw, index) };
92        let len = usize::try_from(len).map_err(|_| DbError::TextTooLarge)?;
93        if len == 0 {
94            return Ok(String::new());
95        }
96        if text.is_null() {
97            return Ok(String::new());
98        }
99        let bytes = unsafe { slice::from_raw_parts(text.cast::<u8>(), len) };
100        Ok(String::from_utf8_lossy(bytes).into_owned())
101    }
102}
103
104impl FromColumn for TextLen {
105    const EXPECTED: &'static str = "TEXT";
106
107    fn read(row: &Row<'_>, index: usize) -> Result<Self, DbError> {
108        let actual = row.column_type(index)?;
109        row.require_type(index, Self::EXPECTED, actual)?;
110        let index = c_int::try_from(index).map_err(|_| DbError::TooManyParameters)?;
111        let len = unsafe { ffi::sqlite3_column_bytes(row.raw, index) };
112        usize::try_from(len)
113            .map(TextLen)
114            .map_err(|_| DbError::TextTooLarge)
115    }
116}
117
118impl FromColumn for i64 {
119    const EXPECTED: &'static str = "INTEGER";
120
121    fn read(row: &Row<'_>, index: usize) -> Result<Self, DbError> {
122        let actual = row.column_type(index)?;
123        row.require_type(index, Self::EXPECTED, actual)?;
124        let index = c_int::try_from(index).map_err(|_| DbError::TooManyParameters)?;
125        Ok(unsafe { ffi::sqlite3_column_int64(row.raw, index) })
126    }
127}
128
129impl FromColumn for f64 {
130    const EXPECTED: &'static str = "REAL";
131
132    fn read(row: &Row<'_>, index: usize) -> Result<Self, DbError> {
133        let actual = row.column_type(index)?;
134        row.require_type(index, Self::EXPECTED, actual)?;
135        let index = c_int::try_from(index).map_err(|_| DbError::TooManyParameters)?;
136        Ok(unsafe { ffi::sqlite3_column_double(row.raw, index) })
137    }
138}
139
140impl FromColumn for Vec<u8> {
141    const EXPECTED: &'static str = "BLOB";
142
143    fn read(row: &Row<'_>, index: usize) -> Result<Self, DbError> {
144        let actual = row.column_type(index)?;
145        row.require_type(index, Self::EXPECTED, actual)?;
146        let index = c_int::try_from(index).map_err(|_| DbError::TooManyParameters)?;
147        let ptr = unsafe { ffi::sqlite3_column_blob(row.raw, index) };
148        let len = unsafe { ffi::sqlite3_column_bytes(row.raw, index) };
149        let len = usize::try_from(len).map_err(|_| DbError::BlobTooLarge)?;
150        if len == 0 {
151            return Ok(Vec::new());
152        }
153        if ptr.is_null() {
154            return Ok(Vec::new());
155        }
156        let bytes = unsafe { slice::from_raw_parts(ptr.cast::<u8>(), len) };
157        Ok(bytes.to_vec())
158    }
159}
160
161impl<T: FromColumn> FromColumn for Option<T> {
162    const EXPECTED: &'static str = T::EXPECTED;
163
164    fn read(row: &Row<'_>, index: usize) -> Result<Self, DbError> {
165        if row.column_type(index)? == ffi::SQLITE_NULL {
166            Ok(None)
167        } else {
168            T::read(row, index).map(Some)
169        }
170    }
171}
172
173fn type_name(code: c_int) -> &'static str {
174    match code {
175        ffi::SQLITE_INTEGER => "INTEGER",
176        ffi::SQLITE_FLOAT => "REAL",
177        ffi::SQLITE_TEXT => "TEXT",
178        ffi::SQLITE_BLOB => "BLOB",
179        ffi::SQLITE_NULL => "NULL",
180        _ => "UNKNOWN",
181    }
182}