sqlx_cloudflare_core/
row.rs1use std::sync::Arc;
2
3use sqlx_core::error::Error;
4
5use crate::{TypeInfo, Value};
6
7pub fn rows<C, R>(
18 names: Vec<String>,
19 rows: Vec<Vec<Value>>,
20 column: impl Fn(String, usize, TypeInfo) -> C,
21 row: impl Fn(Arc<[C]>, Box<[Value]>) -> R,
22) -> Result<Vec<R>, Error> {
23 let columns: Arc<[C]> = names
24 .into_iter()
25 .enumerate()
26 .map(|(ordinal, name)| {
27 let type_info = rows
28 .iter()
29 .filter_map(|row| row.get(ordinal))
30 .map(Value::type_info)
31 .find(|type_info| *type_info != TypeInfo::Null)
32 .unwrap_or(TypeInfo::Null);
33
34 column(name, ordinal, type_info)
35 })
36 .collect();
37
38 rows.into_iter()
39 .map(|values| {
40 if values.len() != columns.len() {
41 return Err(Error::Protocol(format!(
42 "a row of {} values came back for {} columns",
43 values.len(),
44 columns.len()
45 )));
46 }
47
48 Ok(row(Arc::clone(&columns), values.into_boxed_slice()))
49 })
50 .collect()
51}
52
53#[cfg(test)]
54mod tests {
55 use super::rows;
56 use crate::{TypeInfo, Value};
57
58 fn types(names: &[&str], values: Vec<Vec<Value>>) -> Vec<TypeInfo> {
59 let names = names.iter().map(|name| (*name).to_owned()).collect();
60 let rows = rows(
61 names,
62 values,
63 |_, _, type_info| type_info,
64 |columns, _| columns,
65 )
66 .unwrap();
67 rows[0].to_vec()
68 }
69
70 #[test]
71 fn a_column_is_typed_by_its_first_non_null_value() {
72 let types = types(
73 &["id", "name", "gone"],
74 vec![
75 vec![Value::Integer(1), Value::Null, Value::Null],
76 vec![Value::Integer(2), Value::Text("b".into()), Value::Null],
77 ],
78 );
79
80 assert_eq!(types, [TypeInfo::Integer, TypeInfo::Text, TypeInfo::Null]);
81 }
82
83 #[test]
84 fn a_short_row_is_an_error() {
85 assert!(rows(
86 vec!["a".into(), "b".into()],
87 vec![vec![Value::Null]],
88 |_, _, _| (),
89 |_, _| ()
90 )
91 .is_err());
92 }
93}