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