use std::sync::Arc;
use keelson_core::{FromValue, Value};
use crate::error::ExecError;
#[derive(Debug, Clone)]
pub struct Column {
name: String,
}
impl Column {
pub fn new(name: impl Into<String>) -> Self {
Column { name: name.into() }
}
pub fn name(&self) -> &str {
&self.name
}
}
#[derive(Debug, Clone)]
pub struct Row {
columns: Arc<[Column]>,
values: Vec<Value>,
}
impl Row {
pub fn new(columns: Arc<[Column]>, values: Vec<Value>) -> Self {
debug_assert_eq!(columns.len(), values.len());
Row { columns, values }
}
pub fn columns(&self) -> &[Column] {
&self.columns
}
pub fn value(&self, name: &str) -> Option<&Value> {
self.index_of(name).map(|i| &self.values[i])
}
pub fn get<T: FromValue>(&self, name: &str) -> Result<T, ExecError> {
let i = self.require(name)?;
decode(&self.columns[i].name, self.values[i].clone())
}
pub fn get_at<T: FromValue>(&self, index: usize) -> Result<T, ExecError> {
let v = self.value_at(index)?.clone();
decode(&positional_label(index, &self.columns), v)
}
pub fn take<T: FromValue>(&mut self, name: &str) -> Result<T, ExecError> {
let i = self.require(name)?;
let v = std::mem::replace(&mut self.values[i], Value::Null);
decode(&self.columns[i].name, v)
}
pub fn take_at<T: FromValue>(&mut self, index: usize) -> Result<T, ExecError> {
if index >= self.values.len() {
return Err(missing(&format!("#{index}"), &self.columns));
}
let v = std::mem::replace(&mut self.values[index], Value::Null);
decode(&positional_label(index, &self.columns), v)
}
fn index_of(&self, name: &str) -> Option<usize> {
self.columns.iter().position(|c| c.name == name)
}
fn require(&self, name: &str) -> Result<usize, ExecError> {
self.index_of(name)
.ok_or_else(|| missing(name, &self.columns))
}
fn value_at(&self, index: usize) -> Result<&Value, ExecError> {
self.values
.get(index)
.ok_or_else(|| missing(&format!("#{index}"), &self.columns))
}
}
fn positional_label(index: usize, columns: &[Column]) -> String {
match columns.get(index) {
Some(c) if !c.name.is_empty() => c.name.clone(),
_ => format!("#{index}"),
}
}
fn missing(column: &str, columns: &[Column]) -> ExecError {
ExecError::MissingColumn {
column: column.to_owned(),
available: columns.iter().map(|c| c.name.clone()).collect(),
}
}
fn decode<T: FromValue>(column: &str, v: Value) -> Result<T, ExecError> {
T::from_value(v).map_err(|source| ExecError::Decode {
column: column.to_owned(),
source,
})
}
pub trait FromRow: Sized {
fn from_row(row: &mut Row) -> Result<Self, ExecError>;
}
impl FromRow for Row {
fn from_row(row: &mut Row) -> Result<Self, ExecError> {
Ok(row.clone())
}
}
macro_rules! from_row_tuple {
($($idx:tt : $t:ident),+) => {
impl<$($t: FromValue),+> FromRow for ($($t,)+) {
fn from_row(row: &mut Row) -> Result<Self, ExecError> {
Ok(($(row.take_at::<$t>($idx)?,)+))
}
}
};
}
from_row_tuple!(0: A);
from_row_tuple!(0: A, 1: B);
from_row_tuple!(0: A, 1: B, 2: C);
from_row_tuple!(0: A, 1: B, 2: C, 3: D);
from_row_tuple!(0: A, 1: B, 2: C, 3: D, 4: E);
from_row_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F);
from_row_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G);
from_row_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H);
#[cfg(test)]
mod tests {
use super::*;
fn row() -> Row {
let columns: Arc<[Column]> =
vec![Column::new("id"), Column::new("name"), Column::new("email")].into();
Row::new(
columns,
vec![Value::I64(7), Value::Text("ada".into()), Value::Null],
)
}
#[test]
fn get_by_name_and_position() {
let r = row();
assert_eq!(r.get::<i64>("id").unwrap(), 7);
assert_eq!(r.get_at::<String>(1).unwrap(), "ada");
assert_eq!(r.get::<Option<String>>("email").unwrap(), None);
assert_eq!(r.value("id"), Some(&Value::I64(7)));
assert_eq!(r.value("nope"), None);
}
#[test]
fn take_moves_the_value_out() {
let mut r = row();
assert_eq!(r.take::<String>("name").unwrap(), "ada");
assert_eq!(r.value("name"), Some(&Value::Null));
}
#[test]
fn null_into_non_option_names_the_column() {
let r = row();
let e = r.get::<String>("email").unwrap_err();
assert_eq!(
e.to_string(),
"column \"email\": cannot read NULL as String"
);
}
#[test]
fn type_mismatch_names_the_column_even_positionally() {
let r = row();
let e = r.get_at::<i64>(1).unwrap_err();
assert_eq!(e.to_string(), "column \"name\": cannot read text as i64");
}
#[test]
fn missing_column_lists_the_available_ones() {
let r = row();
let e = r.get::<i64>("emial").unwrap_err();
assert_eq!(
e.to_string(),
"no column \"emial\" in result set (columns: id, name, email)"
);
let e = r.get_at::<i64>(9).unwrap_err();
assert!(e.to_string().contains("#9"));
}
#[test]
fn tuples_read_positionally() {
let mut r = row();
let (id, name, email) = <(i64, String, Option<String>)>::from_row(&mut r).unwrap();
assert_eq!((id, name.as_str(), email), (7, "ada", None));
}
#[test]
fn a_struct_maps_by_name_with_the_documented_pattern() {
struct User {
id: i64,
name: String,
email: Option<String>,
}
impl FromRow for User {
fn from_row(row: &mut Row) -> Result<Self, ExecError> {
Ok(User {
id: row.take("id")?,
name: row.take("name")?,
email: row.take("email")?,
})
}
}
let u = User::from_row(&mut row()).unwrap();
assert_eq!((u.id, u.name.as_str(), u.email), (7, "ada", None));
}
}