From 45a6d94345d3b6e27e6526b8ff32f22261266e5e Mon Sep 17 00:00:00 2001 From: itowlson Date: Wed, 27 Aug 2025 15:43:38 +1200 Subject: [PATCH] PostgreSQL result set helper for column-oriented lookup Signed-off-by: itowlson --- examples/postgres-v4/src/lib.rs | 11 +++--- src/pg4.rs | 61 +++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/examples/postgres-v4/src/lib.rs b/examples/postgres-v4/src/lib.rs index bc253bd..2e77575 100644 --- a/examples/postgres-v4/src/lib.rs +++ b/examples/postgres-v4/src/lib.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] use anyhow::Result; use http::{Request, Response}; -use spin_sdk::{http_component, pg3, pg3::Decode}; +use spin_sdk::{http_component, pg4}; // The environment variable set in `spin.toml` that points to the // address of the Pg server that the component will write to @@ -10,7 +10,7 @@ const DB_URL_ENV: &str = "DB_URL"; #[http_component] fn process(req: Request<()>) -> Result> { let address = std::env::var(DB_URL_ENV)?; - let conn = pg3::Connection::open(&address)?; + let conn = pg4::Connection::open(&address)?; let year_header = req .headers() @@ -31,10 +31,9 @@ fn process(req: Request<()>) -> Result> { "it was anarchy".to_owned() } else { let ruler_names = rulers - .rows - .into_iter() - .map(|r| Decode::decode(&r[0])) - .collect::, _>>()?; + .rows() + .map(|r| r.get::("name").unwrap()) + .collect::>(); ruler_names.join(" and ") }; diff --git a/src/pg4.rs b/src/pg4.rs index ba05ebb..15be659 100644 --- a/src/pg4.rs +++ b/src/pg4.rs @@ -133,6 +133,67 @@ pub use super::wit::pg4::Connection; /// ``` pub use super::wit::pg4::RowSet; +impl RowSet { + /// Get all the rows for this query result + pub fn rows(&self) -> impl Iterator> { + self.rows.iter().map(|r| Row { + columns: self.columns.as_slice(), + result: r, + }) + } +} + +/// A database row result. +/// +/// There are two representations of a SQLite row in the SDK. This type is useful for +/// addressing elements by column name, and is obtained from the [RowSet::rows()] function. +/// The [DbValue] vector representation is obtained from the [field@RowSet::rows] field, and provides +/// index-based lookup or low-level access to row values via a vector. +pub struct Row<'a> { + columns: &'a [super::wit::pg4::Column], + result: &'a [DbValue], +} + +impl Row<'_> { + /// Get a value by its column name. The value is converted to the target type as per the + /// conversion table shown in the module documentation. + /// + /// This function returns None for both no such column _and_ failed conversion. You should use + /// it only if you do not need to address errors (that is, if you know that conversion should + /// never fail). If your code does not know the type in advance, use the raw [field@RowSet::rows] vector + /// instead of the `Row` wrapper to access the underlying [DbValue] enum: this will allow you to + /// determine the type and process it accordingly. + /// + /// Additionally, this function performs a name lookup each time it is called. If you are iterating + /// over a large number of rows, it's more efficient to use column indexes, either calculated or + /// statically known from the column order in the SQL. + /// + /// # Examples + /// + /// ```no_run + /// use spin_sdk::pg4::{Connection, DbValue}; + /// + /// # fn main() -> anyhow::Result<()> { + /// # let user_id = 0; + /// let db = Connection::open("host=localhost user=postgres password=my_password dbname=mydb")?; + /// let query_result = db.query( + /// "SELECT * FROM users WHERE id = $1", + /// &[user_id.into()] + /// )?; + /// let user_row = query_result.rows().next().unwrap(); + /// + /// let name = user_row.get::("name").unwrap(); + /// let age = user_row.get::("age").unwrap(); + /// # Ok(()) + /// # } + /// ``` + pub fn get(&self, column: &str) -> Option { + let i = self.columns.iter().position(|c| c.name == column)?; + let db_value = self.result.get(i)?; + Decode::decode(db_value).ok() + } +} + #[doc(inline)] pub use super::wit::pg4::{Error as PgError, *};