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::column::ColumnIndex;
8use sqlx_core::database::Database;
9use sqlx_core::error::Error;
10use sqlx_core::row::Row;
11use sqlx_core::HashMap;
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.columns.get(ord).ok_or_else(|| Error::ColumnIndexOutOfBounds {
63            index: ord,
64            len: self.columns.len(),
65        })?;
66        let val = self.values.get(ord).ok_or_else(|| Error::ColumnIndexOutOfBounds {
67            index: ord,
68            len: self.values.len(),
69        })?;
70        Ok(SpgValueRef::new(val, col.type_info().clone()))
71    }
72}
73
74impl ColumnIndex<SpgRow> for &str {
75    fn index(&self, row: &SpgRow) -> Result<usize, Error> {
76        row.by_name
77            .get(*self)
78            .copied()
79            .ok_or_else(|| Error::ColumnNotFound((*self).to_string()))
80    }
81}
82
83impl ColumnIndex<SpgRow> for usize {
84    fn index(&self, row: &SpgRow) -> Result<usize, Error> {
85        if *self >= row.columns.len() {
86            return Err(Error::ColumnIndexOutOfBounds {
87                index: *self,
88                len: row.columns.len(),
89            });
90        }
91        Ok(*self)
92    }
93}
94
95// `Column::type_info` returns `&<Self::Database as Database>::TypeInfo`
96// which is the path Row::try_get_raw above relies on; pull it in
97// at the call site via the sqlx-core trait we already imported
98// to keep clippy happy when downstream consumers turn on the
99// `unused_imports` lint.
100const _: fn() = || {
101    fn _ensure_column_trait<C: sqlx_core::column::Column>(c: &C)
102    where
103        C::Database: Database,
104    {
105        let _ = c.type_info();
106    }
107};