trailbase-sqlite 0.3.0

Async SQLite connection for TrailBase
Documentation
use base64::prelude::*;
use rusqlite::{Statement, types};
use std::fmt::Debug;
use std::ops::Index;
use std::str::FromStr;
use std::sync::Arc;
use thiserror::Error;

#[derive(Debug, Copy, Clone)]
pub enum ValueType {
  Integer = 1,
  Real,
  Text,
  Blob,
  Null,
}

impl FromStr for ValueType {
  type Err = ();

  fn from_str(s: &str) -> std::result::Result<ValueType, Self::Err> {
    match s {
      "TEXT" => Ok(ValueType::Text),
      "INTEGER" => Ok(ValueType::Integer),
      "BLOB" => Ok(ValueType::Blob),
      "NULL" => Ok(ValueType::Null),
      "REAL" => Ok(ValueType::Real),
      _ => Err(()),
    }
  }
}

#[derive(Debug, Clone)]
pub struct Column {
  name: String,
  decl_type: Option<ValueType>,
}

#[derive(Debug)]
pub struct Rows(pub(crate) Vec<Row>, pub(crate) Arc<Vec<Column>>);

pub(crate) fn columns(stmt: &Statement<'_>) -> Vec<Column> {
  return stmt
    .columns()
    .into_iter()
    .map(|c| Column {
      name: c.name().to_string(),
      decl_type: c.decl_type().and_then(|s| ValueType::from_str(s).ok()),
    })
    .collect();
}

impl Rows {
  pub fn from_rows(mut rows: rusqlite::Rows) -> rusqlite::Result<Self> {
    let columns: Arc<Vec<Column>> = Arc::new(rows.as_ref().map_or(vec![], columns));

    let mut result = vec![];
    while let Some(row) = rows.next()? {
      result.push(Row::from_row(row, Some(columns.clone()))?);
    }

    return Ok(Self(result, columns));
  }

  pub fn len(&self) -> usize {
    return self.0.len();
  }

  pub fn is_empty(&self) -> bool {
    return self.0.is_empty();
  }

  pub fn iter(&self) -> std::slice::Iter<'_, Row> {
    return self.0.iter();
  }

  pub fn get(&self, idx: usize) -> Option<&Row> {
    return self.0.get(idx);
  }

  pub fn last(&self) -> Option<&Row> {
    return self.0.last();
  }

  pub fn column_count(&self) -> usize {
    return self.1.len();
  }

  pub fn column_names(&self) -> Vec<&str> {
    return self.1.iter().map(|s| s.name.as_str()).collect();
  }

  pub fn column_name(&self, idx: usize) -> Option<&str> {
    return self.1.get(idx).map(|c| c.name.as_str());
  }

  pub fn column_type(&self, idx: usize) -> std::result::Result<ValueType, rusqlite::Error> {
    if let Some(c) = self.1.get(idx) {
      return c.decl_type.ok_or_else(|| {
        rusqlite::Error::InvalidColumnType(
          idx,
          self.column_name(idx).unwrap_or("?").to_string(),
          types::Type::Null,
        )
      });
    }

    return Err(rusqlite::Error::InvalidColumnType(
      idx,
      self.column_name(idx).unwrap_or("?").to_string(),
      types::Type::Null,
    ));
  }
}

impl Index<usize> for Rows {
  type Output = Row;

  fn index(&self, idx: usize) -> &Self::Output {
    return &self.0[idx];
  }
}

impl IntoIterator for Rows {
  type Item = Row;
  type IntoIter = std::vec::IntoIter<Self::Item>;

  fn into_iter(self) -> Self::IntoIter {
    return self.0.into_iter();
  }
}

#[derive(Debug)]
pub struct Row(Vec<types::Value>, Arc<Vec<Column>>);

impl Row {
  pub fn from_row(row: &rusqlite::Row, cols: Option<Arc<Vec<Column>>>) -> rusqlite::Result<Self> {
    let columns = cols.unwrap_or_else(|| Arc::new(columns(row.as_ref())));

    let values = (0..columns.len())
      .map(|idx| Ok(row.get_ref(idx)?.into()))
      .collect::<Result<Vec<types::Value>, rusqlite::Error>>()?;

    return Ok(Self(values, columns));
  }

  pub fn split_off(&mut self, at: usize) -> Row {
    let split_values = self.0.split_off(at);
    let mut columns = (*self.1).clone();
    let split_columns = columns.split_off(at);
    self.1 = Arc::new(columns);
    return Row(split_values, Arc::new(split_columns));
  }

  pub fn get<T>(&self, idx: usize) -> types::FromSqlResult<T>
  where
    T: types::FromSql,
  {
    let Some(value) = self.0.get(idx) else {
      return Err(types::FromSqlError::OutOfRange(idx as i64));
    };
    return T::column_result(value.into());
  }

  pub fn get_value(&self, idx: usize) -> Option<&types::Value> {
    return self.0.get(idx);
  }

  pub fn len(&self) -> usize {
    assert_eq!(self.1.len(), self.0.len());
    return self.0.len();
  }

  pub fn is_empty(&self) -> bool {
    return self.0.is_empty();
  }

  pub fn last(&self) -> Option<&types::Value> {
    return self.0.last();
  }

  pub fn column_count(&self) -> usize {
    assert_eq!(self.1.len(), self.0.len());
    return self.1.len();
  }

  pub fn column_names(&self) -> Vec<&str> {
    return self.1.iter().map(|s| s.name.as_str()).collect();
  }

  pub fn column_name(&self, idx: usize) -> Option<&str> {
    return self.1.get(idx).map(|c| c.name.as_str());
  }
}

impl Index<usize> for Row {
  type Output = types::Value;

  fn index(&self, idx: usize) -> &Self::Output {
    return &self.0[idx];
  }
}

#[derive(Debug, Error)]
pub enum JsonError {
  #[error("Float not finite")]
  Finite,
  #[error("Value not found")]
  ValueNotFound,
}

pub fn value_to_json(value: &types::Value) -> Result<serde_json::Value, JsonError> {
  return Ok(match value {
    types::Value::Null => serde_json::Value::Null,
    types::Value::Real(real) => {
      let Some(number) = serde_json::Number::from_f64(*real) else {
        return Err(JsonError::Finite);
      };
      serde_json::Value::Number(number)
    }
    types::Value::Integer(integer) => serde_json::Value::Number(serde_json::Number::from(*integer)),
    types::Value::Blob(blob) => serde_json::Value::String(BASE64_URL_SAFE.encode(blob)),
    types::Value::Text(text) => serde_json::Value::String(text.clone()),
  });
}

pub fn row_to_json_array(row: &Row) -> Result<Vec<serde_json::Value>, JsonError> {
  let cols = row.column_count();
  let mut json_row = Vec::<serde_json::Value>::with_capacity(cols);

  for i in 0..cols {
    let value = row.get_value(i).ok_or(JsonError::ValueNotFound)?;
    json_row.push(value_to_json(value)?);
  }

  return Ok(json_row);
}

pub fn rows_to_json_arrays(rows: &Rows) -> Result<Vec<Vec<serde_json::Value>>, JsonError> {
  return rows.iter().map(row_to_json_array).collect();
}