1use 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}