use std::sync::Arc;
use pg_wired::protocol::types::RawRow;
use crate::decode::{Decode, DecodeText};
use crate::error::TypedError;
#[derive(Debug)]
pub(crate) struct RowSchema {
pub(crate) columns: Vec<String>,
pub(crate) type_oids: Vec<u32>,
pub(crate) formats: Vec<i16>,
}
impl RowSchema {
pub(crate) fn empty() -> Self {
Self {
columns: Vec::new(),
type_oids: Vec::new(),
formats: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct Row {
pub(crate) schema: Arc<RowSchema>,
pub(crate) data: RawRow,
}
impl Row {
pub fn get<T: Decode + DecodeText>(&self, idx: usize) -> Result<T, TypedError> {
let cell = self.data.try_cell(idx).ok_or(TypedError::Decode {
column: idx,
message: format!("column index {idx} out of range (have {})", self.data.len()),
})?;
let bytes = cell.ok_or(TypedError::UnexpectedNull(idx))?;
let format = self.schema.formats.get(idx).copied().unwrap_or(0);
if format == 1 {
T::decode(bytes)
} else {
let s = std::str::from_utf8(bytes).map_err(|e| TypedError::Decode {
column: idx,
message: format!("invalid UTF-8: {e}"),
})?;
T::decode_text(s)
}
}
pub fn get_opt<T: Decode + DecodeText>(&self, idx: usize) -> Result<Option<T>, TypedError> {
let cell = self.data.try_cell(idx).ok_or(TypedError::Decode {
column: idx,
message: format!("column index {idx} out of range"),
})?;
match cell {
None => Ok(None),
Some(bytes) => {
let format = self.schema.formats.get(idx).copied().unwrap_or(0);
if format == 1 {
Ok(Some(T::decode(bytes)?))
} else {
let s = std::str::from_utf8(bytes).map_err(|e| TypedError::Decode {
column: idx,
message: format!("invalid UTF-8: {e}"),
})?;
Ok(Some(T::decode_text(s)?))
}
}
}
}
pub fn get_by_name<T: Decode + DecodeText>(&self, name: &str) -> Result<T, TypedError> {
let idx = self.column_index(name)?;
self.get(idx)
}
pub fn get_opt_by_name<T: Decode + DecodeText>(
&self,
name: &str,
) -> Result<Option<T>, TypedError> {
let idx = self.column_index(name)?;
self.get_opt(idx)
}
pub fn has_column(&self, name: &str) -> bool {
self.schema.columns.iter().any(|c| c == name)
}
fn column_index(&self, name: &str) -> Result<usize, TypedError> {
self.schema
.columns
.iter()
.position(|c| c == name)
.ok_or_else(|| TypedError::ColumnNotFound(name.to_string()))
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn raw(&self, idx: usize) -> Option<&[u8]> {
self.data.cell(idx)
}
pub fn column_name(&self, idx: usize) -> Option<&str> {
self.schema.columns.get(idx).map(|s| s.as_str())
}
pub fn column_type_oid(&self, idx: usize) -> Option<u32> {
self.schema.type_oids.get(idx).copied()
}
}
pub trait FromRow: Sized {
fn from_row(row: &Row) -> Result<Self, TypedError>;
}