1use std::sync::Arc;
2
3use keelson_core::{FromValue, Value};
4
5use crate::error::ExecError;
6
7#[derive(Debug, Clone)]
9pub struct Column {
10 name: String,
11}
12
13impl Column {
14 pub fn new(name: impl Into<String>) -> Self {
16 Column { name: name.into() }
17 }
18
19 pub fn name(&self) -> &str {
21 &self.name
22 }
23}
24
25#[derive(Debug, Clone)]
33pub struct Row {
34 columns: Arc<[Column]>,
35 values: Vec<Value>,
36}
37
38impl Row {
39 pub fn new(columns: Arc<[Column]>, values: Vec<Value>) -> Self {
42 debug_assert_eq!(columns.len(), values.len());
43 Row { columns, values }
44 }
45
46 pub fn columns(&self) -> &[Column] {
48 &self.columns
49 }
50
51 pub fn value(&self, name: &str) -> Option<&Value> {
56 self.index_of(name).map(|i| &self.values[i])
57 }
58
59 pub fn get<T: FromValue>(&self, name: &str) -> Result<T, ExecError> {
62 let i = self.require(name)?;
63 decode(&self.columns[i].name, self.values[i].clone())
64 }
65
66 pub fn get_at<T: FromValue>(&self, index: usize) -> Result<T, ExecError> {
68 let v = self.value_at(index)?.clone();
69 decode(&positional_label(index, &self.columns), v)
70 }
71
72 pub fn take<T: FromValue>(&mut self, name: &str) -> Result<T, ExecError> {
77 let i = self.require(name)?;
78 let v = std::mem::replace(&mut self.values[i], Value::Null);
79 decode(&self.columns[i].name, v)
80 }
81
82 pub fn take_at<T: FromValue>(&mut self, index: usize) -> Result<T, ExecError> {
84 if index >= self.values.len() {
85 return Err(missing(&format!("#{index}"), &self.columns));
86 }
87 let v = std::mem::replace(&mut self.values[index], Value::Null);
88 decode(&positional_label(index, &self.columns), v)
89 }
90
91 fn index_of(&self, name: &str) -> Option<usize> {
92 self.columns.iter().position(|c| c.name == name)
93 }
94
95 fn require(&self, name: &str) -> Result<usize, ExecError> {
96 self.index_of(name)
97 .ok_or_else(|| missing(name, &self.columns))
98 }
99
100 fn value_at(&self, index: usize) -> Result<&Value, ExecError> {
101 self.values
102 .get(index)
103 .ok_or_else(|| missing(&format!("#{index}"), &self.columns))
104 }
105}
106
107fn positional_label(index: usize, columns: &[Column]) -> String {
110 match columns.get(index) {
111 Some(c) if !c.name.is_empty() => c.name.clone(),
112 _ => format!("#{index}"),
113 }
114}
115
116fn missing(column: &str, columns: &[Column]) -> ExecError {
117 ExecError::MissingColumn {
118 column: column.to_owned(),
119 available: columns.iter().map(|c| c.name.clone()).collect(),
120 }
121}
122
123fn decode<T: FromValue>(column: &str, v: Value) -> Result<T, ExecError> {
127 T::from_value(v).map_err(|source| ExecError::Decode {
128 column: column.to_owned(),
129 source,
130 })
131}
132
133pub trait FromRow: Sized {
158 fn from_row(row: &mut Row) -> Result<Self, ExecError>;
160}
161
162impl FromRow for Row {
164 fn from_row(row: &mut Row) -> Result<Self, ExecError> {
165 Ok(row.clone())
166 }
167}
168
169macro_rules! from_row_tuple {
170 ($($idx:tt : $t:ident),+) => {
171 impl<$($t: FromValue),+> FromRow for ($($t,)+) {
173 fn from_row(row: &mut Row) -> Result<Self, ExecError> {
174 Ok(($(row.take_at::<$t>($idx)?,)+))
175 }
176 }
177 };
178}
179
180from_row_tuple!(0: A);
181from_row_tuple!(0: A, 1: B);
182from_row_tuple!(0: A, 1: B, 2: C);
183from_row_tuple!(0: A, 1: B, 2: C, 3: D);
184from_row_tuple!(0: A, 1: B, 2: C, 3: D, 4: E);
185from_row_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F);
186from_row_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G);
187from_row_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H);
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 fn row() -> Row {
194 let columns: Arc<[Column]> =
195 vec![Column::new("id"), Column::new("name"), Column::new("email")].into();
196 Row::new(
197 columns,
198 vec![Value::I64(7), Value::Text("ada".into()), Value::Null],
199 )
200 }
201
202 #[test]
203 fn get_by_name_and_position() {
204 let r = row();
205 assert_eq!(r.get::<i64>("id").unwrap(), 7);
206 assert_eq!(r.get_at::<String>(1).unwrap(), "ada");
207 assert_eq!(r.get::<Option<String>>("email").unwrap(), None);
208 assert_eq!(r.value("id"), Some(&Value::I64(7)));
209 assert_eq!(r.value("nope"), None);
210 }
211
212 #[test]
213 fn take_moves_the_value_out() {
214 let mut r = row();
215 assert_eq!(r.take::<String>("name").unwrap(), "ada");
216 assert_eq!(r.value("name"), Some(&Value::Null));
218 }
219
220 #[test]
221 fn null_into_non_option_names_the_column() {
222 let r = row();
223 let e = r.get::<String>("email").unwrap_err();
224 assert_eq!(
225 e.to_string(),
226 "column \"email\": cannot read NULL as String"
227 );
228 }
229
230 #[test]
231 fn type_mismatch_names_the_column_even_positionally() {
232 let r = row();
233 let e = r.get_at::<i64>(1).unwrap_err();
234 assert_eq!(e.to_string(), "column \"name\": cannot read text as i64");
235 }
236
237 #[test]
238 fn missing_column_lists_the_available_ones() {
239 let r = row();
240 let e = r.get::<i64>("emial").unwrap_err();
241 assert_eq!(
242 e.to_string(),
243 "no column \"emial\" in result set (columns: id, name, email)"
244 );
245 let e = r.get_at::<i64>(9).unwrap_err();
246 assert!(e.to_string().contains("#9"));
247 }
248
249 #[test]
250 fn tuples_read_positionally() {
251 let mut r = row();
252 let (id, name, email) = <(i64, String, Option<String>)>::from_row(&mut r).unwrap();
253 assert_eq!((id, name.as_str(), email), (7, "ada", None));
254 }
255
256 #[test]
257 fn a_struct_maps_by_name_with_the_documented_pattern() {
258 struct User {
259 id: i64,
260 name: String,
261 email: Option<String>,
262 }
263 impl FromRow for User {
264 fn from_row(row: &mut Row) -> Result<Self, ExecError> {
265 Ok(User {
266 id: row.take("id")?,
267 name: row.take("name")?,
268 email: row.take("email")?,
269 })
270 }
271 }
272 let u = User::from_row(&mut row()).unwrap();
273 assert_eq!((u.id, u.name.as_str(), u.email), (7, "ada", None));
274 }
275}