Skip to main content

spg_sqlx/
row.rs

1//! v7.16.0 — `sqlx::Row` for SPG result rows. Stores the
2//! engine-side `Vec<Value>` + the column metadata so `try_get` /
3//! `try_get_raw` can drive both index- and name-based lookup.
4
5use std::sync::Arc;
6
7use sqlx_core::HashMap;
8use sqlx_core::column::ColumnIndex;
9use sqlx_core::database::Database;
10use sqlx_core::error::Error;
11use sqlx_core::row::Row;
12
13use spg_embedded::Value as EngineValue;
14
15use crate::column::SpgColumn;
16use crate::database::Spg;
17use crate::value::SpgValueRef;
18
19/// A single result row from an SPG-shape SELECT.
20#[derive(Debug, Clone)]
21pub struct SpgRow {
22    /// Column metadata, shared across every row in the same
23    /// fetch — `Arc` so 1-row and 1000-row result sets pay the
24    /// same per-row cost.
25    columns: Arc<Vec<SpgColumn>>,
26    /// Name → ordinal lookup, also shared.
27    by_name: Arc<HashMap<String, usize>>,
28    /// The cell values.
29    values: Vec<EngineValue>,
30}
31
32impl SpgRow {
33    /// Construct a row given the shared column metadata + the
34    /// cell values for this specific row. Adapter-internal.
35    #[must_use]
36    pub fn new(
37        columns: Arc<Vec<SpgColumn>>,
38        by_name: Arc<HashMap<String, usize>>,
39        values: Vec<EngineValue>,
40    ) -> Self {
41        Self {
42            columns,
43            by_name,
44            values,
45        }
46    }
47}
48
49impl Row for SpgRow {
50    type Database = Spg;
51
52    fn columns(&self) -> &[SpgColumn] {
53        &self.columns
54    }
55
56    fn try_get_raw<I>(&self, index: I) -> Result<SpgValueRef<'_>, Error>
57    where
58        I: ColumnIndex<Self>,
59    {
60        use sqlx_core::column::Column as _;
61        let ord = index.index(self)?;
62        let col = self
63            .columns
64            .get(ord)
65            .ok_or_else(|| Error::ColumnIndexOutOfBounds {
66                index: ord,
67                len: self.columns.len(),
68            })?;
69        let val = self
70            .values
71            .get(ord)
72            .ok_or_else(|| Error::ColumnIndexOutOfBounds {
73                index: ord,
74                len: self.values.len(),
75            })?;
76        Ok(SpgValueRef::new(val, col.type_info().clone()))
77    }
78}
79
80impl ColumnIndex<SpgRow> for &str {
81    fn index(&self, row: &SpgRow) -> Result<usize, Error> {
82        row.by_name
83            .get(*self)
84            .copied()
85            .ok_or_else(|| Error::ColumnNotFound((*self).to_string()))
86    }
87}
88
89impl ColumnIndex<SpgRow> for usize {
90    fn index(&self, row: &SpgRow) -> Result<usize, Error> {
91        if *self >= row.columns.len() {
92            return Err(Error::ColumnIndexOutOfBounds {
93                index: *self,
94                len: row.columns.len(),
95            });
96        }
97        Ok(*self)
98    }
99}
100
101// `Column::type_info` returns `&<Self::Database as Database>::TypeInfo`
102// which is the path Row::try_get_raw above relies on; pull it in
103// at the call site via the sqlx-core trait we already imported
104// to keep clippy happy when downstream consumers turn on the
105// `unused_imports` lint.
106const _: fn() = || {
107    fn _ensure_column_trait<C: sqlx_core::column::Column>(c: &C)
108    where
109        C::Database: Database,
110    {
111        let _ = c.type_info();
112    }
113};