use std::fmt;
use crate::{OxiSqlError, Value};
pub trait FromValue: Sized {
fn from_value(v: &Value) -> Result<Self, OxiSqlError>;
}
impl FromValue for bool {
fn from_value(v: &Value) -> Result<Self, OxiSqlError> {
match v {
Value::Bool(b) => Ok(*b),
other => Err(OxiSqlError::TypeMismatch {
expected: "Bool",
got: other.type_name(),
}),
}
}
}
impl FromValue for i32 {
fn from_value(v: &Value) -> Result<Self, OxiSqlError> {
match v {
Value::I64(n) => i32::try_from(*n).map_err(|_| OxiSqlError::TypeMismatch {
expected: "I64 (within i32 range)",
got: "I64 (out of i32 range)",
}),
other => Err(OxiSqlError::TypeMismatch {
expected: "I64",
got: other.type_name(),
}),
}
}
}
impl FromValue for i64 {
fn from_value(v: &Value) -> Result<Self, OxiSqlError> {
match v {
Value::I64(n) => Ok(*n),
other => Err(OxiSqlError::TypeMismatch {
expected: "I64",
got: other.type_name(),
}),
}
}
}
impl FromValue for f64 {
fn from_value(v: &Value) -> Result<Self, OxiSqlError> {
match v {
Value::F64(n) => Ok(*n),
Value::I64(n) => Ok(*n as f64),
other => Err(OxiSqlError::TypeMismatch {
expected: "F64",
got: other.type_name(),
}),
}
}
}
impl FromValue for String {
fn from_value(v: &Value) -> Result<Self, OxiSqlError> {
match v {
Value::Text(s) => Ok(s.clone()),
Value::Json(s) => Ok(s.clone()),
Value::Decimal(s) => Ok(s.clone()),
Value::Uuid(_) => Ok(format!("{v}")),
other => Err(OxiSqlError::TypeMismatch {
expected: "Text",
got: other.type_name(),
}),
}
}
}
impl FromValue for Vec<u8> {
fn from_value(v: &Value) -> Result<Self, OxiSqlError> {
match v {
Value::Blob(b) => Ok(b.clone()),
other => Err(OxiSqlError::TypeMismatch {
expected: "Blob",
got: other.type_name(),
}),
}
}
}
impl<T: FromValue> FromValue for Option<T> {
fn from_value(v: &Value) -> Result<Self, OxiSqlError> {
if v.is_null() {
Ok(None)
} else {
T::from_value(v).map(Some)
}
}
}
impl FromValue for u128 {
fn from_value(v: &Value) -> Result<Self, OxiSqlError> {
match v {
Value::Uuid(u) => Ok(*u),
other => Err(OxiSqlError::TypeMismatch {
expected: "Uuid",
got: other.type_name(),
}),
}
}
}
#[derive(Debug, Clone)]
pub struct Row {
columns: Vec<String>,
values: Vec<Value>,
index: std::collections::HashMap<String, usize>,
}
impl Row {
pub fn new(columns: Vec<String>, values: Vec<Value>) -> Self {
let index = columns
.iter()
.enumerate()
.map(|(i, c)| (c.clone(), i))
.collect();
Self {
columns,
values,
index,
}
}
pub fn get(&self, col: &str) -> Option<&Value> {
self.index.get(col).and_then(|&i| self.values.get(i))
}
pub fn get_by_index(&self, i: usize) -> Option<&Value> {
self.values.get(i)
}
pub fn try_get<T: FromValue>(&self, col: &str) -> Result<T, OxiSqlError> {
let val = self
.get(col)
.ok_or_else(|| OxiSqlError::Other(format!("column '{col}' not found")))?;
T::from_value(val)
}
pub fn try_get_by_index<T: FromValue>(&self, i: usize) -> Result<T, OxiSqlError> {
let val = self
.get_by_index(i)
.ok_or_else(|| OxiSqlError::Other(format!("column index {i} out of range")))?;
T::from_value(val)
}
pub fn columns(&self) -> &[String] {
&self.columns
}
pub fn values(&self) -> &[Value] {
&self.values
}
pub fn column_count(&self) -> usize {
self.columns.len()
}
pub fn is_null(&self, col: &str) -> bool {
self.get(col).is_some_and(|v| v.is_null())
}
pub fn into_values(self) -> Vec<Value> {
self.values
}
}
impl fmt::Display for Row {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{{")?;
for (i, (col, val)) in self.columns.iter().zip(self.values.iter()).enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{col}: {val}")?;
}
write!(f, "}}")
}
}
#[derive(Debug, Clone)]
pub struct RowSet {
columns: Vec<String>,
rows: Vec<Row>,
}
impl RowSet {
pub fn from_rows(rows: Vec<Row>) -> Self {
let columns = rows
.first()
.map(|r| r.columns().to_vec())
.unwrap_or_default();
Self { columns, rows }
}
pub fn new(columns: Vec<String>, rows: Vec<Row>) -> Self {
Self { columns, rows }
}
pub fn columns(&self) -> &[String] {
&self.columns
}
pub fn rows(&self) -> &[Row] {
&self.rows
}
pub fn len(&self) -> usize {
self.rows.len()
}
pub fn is_empty(&self) -> bool {
self.rows.is_empty()
}
pub fn column_count(&self) -> usize {
self.columns.len()
}
pub fn into_rows(self) -> Vec<Row> {
self.rows
}
}