Skip to main content

sqlx_cloudflare_do/
row.rs

1use std::fmt::{self, Debug, Formatter};
2use std::sync::Arc;
3
4use sqlx_cloudflare_core::Value;
5use sqlx_core::column::ColumnIndex;
6use sqlx_core::error::Error;
7use sqlx_core::row::Row;
8
9use crate::{Do, DoColumn, DoValueRef};
10
11/// A row of a Durable Object storage result.
12pub struct DoRow {
13    /// Shared by every row of one result.
14    pub(crate) columns: Arc<[DoColumn]>,
15    pub(crate) values: Box<[Value]>,
16}
17
18impl DoRow {
19    /// The rows of one result, from the cursor's `columnNames` and the rows
20    /// `raw()` yields.
21    ///
22    /// Each column is typed by its first non-NULL value, since the cursor
23    /// reports no declared types. Every row shares the one column list.
24    pub(crate) fn from_result(
25        names: Vec<String>,
26        rows: Vec<Vec<Value>>,
27    ) -> Result<Vec<DoRow>, Error> {
28        sqlx_cloudflare_core::rows(
29            names,
30            rows,
31            |name, ordinal, type_info| DoColumn {
32                name,
33                ordinal,
34                type_info,
35            },
36            |columns, values| DoRow { columns, values },
37        )
38    }
39}
40
41impl Row for DoRow {
42    type Database = Do;
43
44    fn columns(&self) -> &[DoColumn] {
45        &self.columns
46    }
47
48    fn try_get_raw<I>(&self, index: I) -> Result<DoValueRef<'_>, Error>
49    where
50        I: ColumnIndex<Self>,
51    {
52        let index = index.index(self)?;
53        Ok(DoValueRef(&self.values[index]))
54    }
55}
56
57impl ColumnIndex<DoRow> for &'_ str {
58    /// The *last* column with this name, which is what sqlx-sqlite returns
59    /// for `SELECT a.id, b.id`: it indexes names into a map, and the later
60    /// insert wins. A linear scan is fine at the width of a real row.
61    fn index(&self, row: &DoRow) -> Result<usize, Error> {
62        row.columns
63            .iter()
64            .rposition(|column| column.name == *self)
65            .ok_or_else(|| Error::ColumnNotFound((*self).into()))
66    }
67}
68
69impl Debug for DoRow {
70    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
71        let mut map = f.debug_map();
72
73        for (column, value) in self.columns.iter().zip(self.values.iter()) {
74            map.entry(&column.name, value);
75        }
76
77        map.finish()
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use sqlx_cloudflare_core::Value;
84    use sqlx_core::row::Row;
85    use sqlx_core::type_info::TypeInfo;
86    use sqlx_core::value::ValueRef;
87
88    use super::DoRow;
89    use crate::DoTypeInfo;
90
91    #[test]
92    fn a_column_is_typed_by_its_first_non_null_value() {
93        let rows = DoRow::from_result(
94            vec!["id".into(), "name".into()],
95            vec![
96                vec![Value::Integer(1), Value::Null],
97                vec![Value::Integer(2), Value::Text("b".into())],
98            ],
99        )
100        .unwrap();
101
102        let types: Vec<_> = rows[0]
103            .columns()
104            .iter()
105            .map(|column| column.type_info)
106            .collect();
107        assert_eq!(types, [DoTypeInfo::Integer, DoTypeInfo::Text]);
108    }
109
110    #[test]
111    fn a_name_finds_the_last_column_that_has_it() {
112        let rows = DoRow::from_result(
113            vec!["id".into(), "id".into()],
114            vec![vec![Value::Integer(1), Value::Integer(2)]],
115        )
116        .unwrap();
117
118        let row = &rows[0];
119        assert_eq!(row.try_get::<i64, _>(0).unwrap(), 1);
120        assert_eq!(row.try_get::<i64, _>("id").unwrap(), 2);
121        assert!(row.try_get_raw("missing").is_err());
122    }
123
124    #[test]
125    fn a_value_carries_its_own_type() {
126        let rows = DoRow::from_result(
127            vec!["n".into()],
128            vec![vec![Value::Integer(1)], vec![Value::Real(1.5)]],
129        )
130        .unwrap();
131
132        // The column says INTEGER, from the first row; the second row's value
133        // is still a REAL, and that is what decoding checks.
134        assert_eq!(rows[1].try_get_raw(0).unwrap().type_info().name(), "REAL");
135        assert!(rows[1].try_get::<i64, _>(0).is_err());
136        assert!((rows[1].try_get::<f64, _>(0).unwrap() - 1.5).abs() < f64::EPSILON);
137    }
138}