use std::collections::BTreeMap;
use crate::catalog::CatalogQuery;
use crate::catalog::error::CatalogError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Value {
Null,
Bool(bool),
SmallInt(i16),
Integer(i64),
Char(char),
Text(String),
TextArray(Vec<String>),
IntegerArray(Vec<i64>),
Bytes(Vec<u8>),
}
#[derive(Debug, Clone, Default)]
pub struct Row {
cols: BTreeMap<String, Value>,
}
impl Row {
#[must_use]
pub const fn new() -> Self {
Self {
cols: BTreeMap::new(),
}
}
#[must_use]
pub fn with(mut self, key: impl Into<String>, value: Value) -> Self {
self.cols.insert(key.into(), value);
self
}
pub fn insert(&mut self, key: impl Into<String>, value: Value) {
self.cols.insert(key.into(), value);
}
pub fn get_value(&self, query: CatalogQuery, key: &str) -> Result<&Value, CatalogError> {
self.cols
.get(key)
.ok_or_else(|| CatalogError::MissingColumn {
query,
column: key.to_string(),
})
}
pub fn is_null(&self, key: &str) -> bool {
matches!(self.cols.get(key), None | Some(Value::Null))
}
pub fn get_int(&self, query: CatalogQuery, key: &str) -> Result<i64, CatalogError> {
match self.get_value(query, key)? {
Value::Integer(i) => Ok(*i),
Value::SmallInt(i) => Ok(i64::from(*i)),
other => Err(CatalogError::BadColumnType {
query,
column: key.to_string(),
message: format!("expected integer, got {other:?}"),
}),
}
}
pub fn get_opt_int(&self, query: CatalogQuery, key: &str) -> Result<Option<i64>, CatalogError> {
if self.is_null(key) {
return Ok(None);
}
self.get_int(query, key).map(Some)
}
pub fn get_text(&self, query: CatalogQuery, key: &str) -> Result<String, CatalogError> {
match self.get_value(query, key)? {
Value::Text(s) => Ok(s.clone()),
other => Err(CatalogError::BadColumnType {
query,
column: key.to_string(),
message: format!("expected text, got {other:?}"),
}),
}
}
pub fn get_opt_text(
&self,
query: CatalogQuery,
key: &str,
) -> Result<Option<String>, CatalogError> {
if self.is_null(key) {
return Ok(None);
}
self.get_text(query, key).map(Some)
}
pub fn get_bool(&self, query: CatalogQuery, key: &str) -> Result<bool, CatalogError> {
match self.get_value(query, key)? {
Value::Bool(b) => Ok(*b),
other => Err(CatalogError::BadColumnType {
query,
column: key.to_string(),
message: format!("expected boolean, got {other:?}"),
}),
}
}
pub fn get_char(&self, query: CatalogQuery, key: &str) -> Result<char, CatalogError> {
match self.get_value(query, key)? {
Value::Char(c) => Ok(*c),
Value::Text(s) if s.chars().count() == 1 => {
Ok(s.chars()
.next()
.unwrap_or_else(|| unreachable!("count==1 has one char")))
}
other => Err(CatalogError::BadColumnType {
query,
column: key.to_string(),
message: format!("expected single-char, got {other:?}"),
}),
}
}
pub fn get_int_array(&self, query: CatalogQuery, key: &str) -> Result<Vec<i64>, CatalogError> {
match self.get_value(query, key)? {
Value::IntegerArray(v) => Ok(v.clone()),
other => Err(CatalogError::BadColumnType {
query,
column: key.to_string(),
message: format!("expected integer array, got {other:?}"),
}),
}
}
pub fn get_text_array(
&self,
query: CatalogQuery,
key: &str,
) -> Result<Vec<String>, CatalogError> {
match self.get_value(query, key)? {
Value::TextArray(v) => Ok(v.clone()),
other => Err(CatalogError::BadColumnType {
query,
column: key.to_string(),
message: format!("expected text array, got {other:?}"),
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::catalog::CatalogQuery;
#[test]
fn missing_column_errors() {
let r = Row::new();
let err = r.get_int(CatalogQuery::Schemas, "oid").unwrap_err();
assert!(matches!(err, CatalogError::MissingColumn { .. }));
}
#[test]
fn integer_widening_works() {
let r = Row::new()
.with("a", Value::SmallInt(7))
.with("b", Value::Integer(42));
assert_eq!(r.get_int(CatalogQuery::Schemas, "a").unwrap(), 7);
assert_eq!(r.get_int(CatalogQuery::Schemas, "b").unwrap(), 42);
}
#[test]
fn null_is_treated_as_absent() {
let r = Row::new().with("c", Value::Null);
assert!(r.is_null("c"));
assert!(
r.get_opt_text(CatalogQuery::Schemas, "c")
.unwrap()
.is_none()
);
}
}