use std::sync::Arc;
use crate::protocol::Oid;
use crate::types::{Format, FromSql};
use crate::error::{PgError, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct FieldDescription {
name: String,
table_oid: Oid,
column_id: i16,
type_oid: Oid,
type_size: i16,
type_modifier: i32,
format: i16,
}
impl FieldDescription {
pub fn new(
name: String,
table_oid: Oid,
column_id: i16,
type_oid: Oid,
type_size: i16,
type_modifier: i32,
format: i16,
) -> Self {
Self {
name,
table_oid,
column_id,
type_oid,
type_size,
type_modifier,
format,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn table_oid(&self) -> Oid {
self.table_oid
}
pub fn column_id(&self) -> i16 {
self.column_id
}
pub fn type_oid(&self) -> Oid {
self.type_oid
}
pub fn type_size(&self) -> i16 {
self.type_size
}
pub fn type_modifier(&self) -> i32 {
self.type_modifier
}
pub fn format(&self) -> i16 {
self.format
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Row {
columns: Arc<Vec<FieldDescription>>,
values: Vec<Option<Vec<u8>>>,
}
impl Row {
pub(crate) fn new(columns: Arc<Vec<FieldDescription>>, values: Vec<Option<Vec<u8>>>) -> Self {
Self { columns, values }
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn columns(&self) -> &[FieldDescription] {
&self.columns
}
pub fn is_null(&self, index: usize) -> bool {
match self.values.get(index) {
Some(value) => value.is_none(),
None => true,
}
}
pub fn get_raw(&self, index: usize) -> Option<&[u8]> {
self.values.get(index).and_then(|v| v.as_deref())
}
#[must_use = "column access errors should be checked"]
pub fn get<T: FromSql>(&self, index: usize) -> Result<T> {
let raw = self.get_raw(index);
let field = self
.columns
.get(index)
.ok_or_else(|| PgError::ColumnIndexOutOfBounds {
index,
count: self.columns.len(),
})?;
let ty = crate::types::Type::from_oid(field.type_oid).unwrap_or_else(|| {
crate::types::Type::new(
"unknown".into(),
0,
crate::types::Kind::Pseudo,
"pg_catalog".into(),
)
});
let format = if field.format() == 1 {
Format::Binary
} else {
Format::Text
};
T::from_sql(&ty, raw, format).map_err(PgError::TypeConversion)
}
#[must_use = "column access errors should be checked"]
pub fn get_by_name<T: FromSql>(&self, name: &str) -> Result<T> {
let index = self
.columns
.iter()
.position(|c| c.name() == name)
.ok_or_else(|| PgError::ColumnNotFound {
name: name.to_string(),
})?;
self.get(index)
}
pub fn column_name(&self, index: usize) -> Option<&str> {
self.columns.get(index).map(|c| c.name())
}
pub fn column_index(&self, name: &str) -> Option<usize> {
self.columns.iter().position(|c| c.name() == name)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_row_get_raw() {
let cols = Arc::new(vec![FieldDescription::new(
"id".into(),
0,
0,
crate::types::INT4_OID,
4,
-1,
0,
)]);
let row = Row::new(cols.clone(), vec![Some(vec![b'1', b'2', b'3'])]);
assert_eq!(row.get_raw(0), Some(b"123".as_slice()));
assert!(!row.is_null(0));
}
#[test]
fn test_row_null() {
let cols = Arc::new(vec![FieldDescription::new(
"name".into(),
0,
0,
crate::types::TEXT_OID,
-1,
-1,
0,
)]);
let row = Row::new(cols, vec![None]);
assert!(row.is_null(0));
assert_eq!(row.get_raw(0), None);
}
#[test]
fn test_row_get_i32() {
let cols = Arc::new(vec![FieldDescription::new(
"id".into(),
0,
0,
crate::types::INT4_OID,
4,
-1,
0,
)]);
let row = Row::new(cols, vec![Some(b"42".to_vec())]);
let val: i32 = row.get(0).unwrap();
assert_eq!(val, 42);
}
#[test]
fn test_row_get_by_name() {
let cols = Arc::new(vec![
FieldDescription::new("id".into(), 0, 0, crate::types::INT4_OID, 4, -1, 0),
FieldDescription::new("name".into(), 0, 0, crate::types::TEXT_OID, -1, -1, 0),
]);
let row = Row::new(cols, vec![Some(b"1".to_vec()), Some(b"alice".to_vec())]);
let id: i32 = row.get_by_name("id").unwrap();
assert_eq!(id, 1);
let name: String = row.get_by_name("name").unwrap();
assert_eq!(name, "alice");
}
#[test]
fn test_row_get_by_name_missing() {
let cols = Arc::new(vec![]);
let row = Row::new(cols, vec![]);
assert!(row.get_by_name::<i32>("missing").is_err());
}
}