use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt;
use std::path::PathBuf;
use anyhow::Result;
use crate::error::{SqawkError, SqawkResult};
use crate::storage::Storage;
#[derive(Debug, Clone)]
pub enum Value {
Null,
Integer(i64),
Float(f64),
String(Cow<'static, str>),
Boolean(bool),
}
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Value::Null, Value::Null) => true,
(Value::Integer(a), Value::Integer(b)) => a == b,
(Value::Float(a), Value::Float(b)) => a == b,
(Value::String(a), Value::String(b)) => a == b,
(Value::Boolean(a), Value::Boolean(b)) => a == b,
(Value::Integer(a), Value::Float(b)) => *a as f64 == *b,
(Value::Float(a), Value::Integer(b)) => *a == *b as f64,
_ => false,
}
}
}
impl Eq for Value {}
impl std::hash::Hash for Value {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
Value::Null => {
0_i32.hash(state);
}
Value::Integer(i) => {
1_i32.hash(state);
i.hash(state);
}
Value::Float(f) => {
2_i32.hash(state);
f.to_bits().hash(state);
}
Value::String(s) => {
3_i32.hash(state);
s.hash(state);
}
Value::Boolean(b) => {
4_i32.hash(state);
b.hash(state);
}
}
}
}
impl PartialOrd for Value {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
use std::cmp::Ordering;
match (self, other) {
(Value::Null, Value::Null) => Some(Ordering::Equal),
(Value::Null, _) => Some(Ordering::Less),
(_, Value::Null) => Some(Ordering::Greater),
(Value::Integer(a), Value::Integer(b)) => a.partial_cmp(b),
(Value::Float(a), Value::Float(b)) => a.partial_cmp(b),
(Value::String(a), Value::String(b)) => a.partial_cmp(b),
(Value::Boolean(a), Value::Boolean(b)) => a.partial_cmp(b),
(Value::Integer(a), Value::Float(b)) => (*a as f64).partial_cmp(b),
(Value::Float(a), Value::Integer(b)) => a.partial_cmp(&(*b as f64)),
(Value::Boolean(_), Value::Integer(_) | Value::Float(_) | Value::String(_)) => {
Some(Ordering::Less)
}
(Value::Integer(_) | Value::Float(_), Value::String(_)) => Some(Ordering::Less),
(Value::String(_), Value::Boolean(_) | Value::Integer(_) | Value::Float(_)) => {
Some(Ordering::Greater)
}
(Value::Integer(_) | Value::Float(_), Value::Boolean(_)) => Some(Ordering::Greater),
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Null => write!(f, "NULL"),
Value::Integer(i) => write!(f, "{}", i),
Value::Float(float) => write!(f, "{}", float),
Value::String(s) => write!(f, "{}", s),
Value::Boolean(b) => write!(f, "{}", b),
}
}
}
impl From<&str> for Value {
fn from(s: &str) -> Self {
if let Ok(i) = s.parse::<i64>() {
return Value::Integer(i);
}
if let Ok(fl) = s.parse::<f64>() {
return Value::Float(fl);
}
match s.to_lowercase().as_str() {
"true" | "yes" | "1" => return Value::Boolean(true),
"false" | "no" | "0" => return Value::Boolean(false),
"" => return Value::Null,
_ => {}
}
Value::String(Cow::Owned(s.to_string()))
}
}
impl From<String> for Value {
fn from(s: String) -> Self {
if let Ok(i) = s.parse::<i64>() {
return Value::Integer(i);
}
if let Ok(fl) = s.parse::<f64>() {
return Value::Float(fl);
}
match s.to_lowercase().as_str() {
"true" | "yes" | "1" => return Value::Boolean(true),
"false" | "no" | "0" => return Value::Boolean(false),
"" => return Value::Null,
_ => {}
}
Value::String(Cow::Owned(s))
}
}
pub type Row = Vec<Value>;
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RowRef {
pub table_idx: usize,
pub row_idx: usize,
}
#[allow(dead_code)]
impl RowRef {
pub fn new(table_idx: usize, row_idx: usize) -> Self {
Self { table_idx, row_idx }
}
pub fn single(row_idx: usize) -> Self {
Self {
table_idx: 0,
row_idx,
}
}
}
#[allow(dead_code)]
#[derive(Debug)]
pub struct RowSet<'a> {
source: &'a Table,
indices: Vec<usize>,
}
#[allow(dead_code)]
impl<'a> RowSet<'a> {
pub fn new(source: &'a Table) -> Self {
Self {
source,
indices: Vec::new(),
}
}
pub fn with_capacity(source: &'a Table, capacity: usize) -> Self {
Self {
source,
indices: Vec::with_capacity(capacity),
}
}
pub fn push(&mut self, row_idx: usize) {
self.indices.push(row_idx);
}
pub fn len(&self) -> usize {
self.indices.len()
}
pub fn is_empty(&self) -> bool {
self.indices.is_empty()
}
pub fn source(&self) -> &'a Table {
self.source
}
pub fn indices(&self) -> &[usize] {
&self.indices
}
pub fn get_value(&self, row_idx: usize, col_idx: usize) -> Option<&Value> {
let actual_row = *self.indices.get(row_idx)?;
self.source.rows().get(actual_row)?.get(col_idx)
}
pub fn materialize_row(&self, row_idx: usize) -> Option<Vec<Value>> {
let actual_row = *self.indices.get(row_idx)?;
self.source.rows().get(actual_row).cloned()
}
pub fn iter(&self) -> impl Iterator<Item = usize> + '_ {
self.indices.iter().copied()
}
}
#[allow(dead_code)]
#[derive(Debug)]
pub struct JoinedRowSet<'a> {
sources: Vec<&'a Table>,
row_pairs: Vec<Vec<Option<usize>>>,
}
#[allow(dead_code)]
impl<'a> JoinedRowSet<'a> {
pub fn new(left: &'a Table, right: &'a Table) -> Self {
Self {
sources: vec![left, right],
row_pairs: Vec::new(),
}
}
pub fn with_capacity(left: &'a Table, right: &'a Table, capacity: usize) -> Self {
Self {
sources: vec![left, right],
row_pairs: Vec::with_capacity(capacity),
}
}
pub fn add_match(&mut self, left_idx: usize, right_idx: usize) {
self.row_pairs.push(vec![Some(left_idx), Some(right_idx)]);
}
pub fn add_left_only(&mut self, left_idx: usize) {
self.row_pairs.push(vec![Some(left_idx), None]);
}
pub fn add_right_only(&mut self, right_idx: usize) {
self.row_pairs.push(vec![None, Some(right_idx)]);
}
pub fn len(&self) -> usize {
self.row_pairs.len()
}
pub fn is_empty(&self) -> bool {
self.row_pairs.is_empty()
}
pub fn get_value(&self, row_idx: usize, table_idx: usize, col_idx: usize) -> &Value {
static NULL_VALUE: Value = Value::Null;
if let Some(row_pair) = self.row_pairs.get(row_idx) {
if let Some(Some(actual_row)) = row_pair.get(table_idx) {
if let Some(table) = self.sources.get(table_idx) {
if let Some(row) = table.rows().get(*actual_row) {
if let Some(value) = row.get(col_idx) {
return value;
}
}
}
}
}
&NULL_VALUE
}
pub fn sources(&self) -> &[&'a Table] {
&self.sources
}
pub fn materialize_row(
&self,
row_idx: usize,
left_cols: usize,
right_cols: usize,
) -> Option<Vec<Value>> {
let _ = self.row_pairs.get(row_idx)?;
let mut result = Vec::with_capacity(left_cols + right_cols);
for col_idx in 0..left_cols {
result.push(self.get_value(row_idx, 0, col_idx).clone());
}
for col_idx in 0..right_cols {
result.push(self.get_value(row_idx, 1, col_idx).clone());
}
Some(result)
}
}
#[allow(dead_code)]
#[derive(Debug)]
pub enum IndexedResult<'a> {
Single(RowSet<'a>),
Joined(JoinedRowSet<'a>),
Materialized(Vec<Vec<Value>>),
}
#[allow(dead_code)]
impl<'a> IndexedResult<'a> {
pub fn len(&self) -> usize {
match self {
IndexedResult::Single(rs) => rs.len(),
IndexedResult::Joined(js) => js.len(),
IndexedResult::Materialized(rows) => rows.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn is_materialized(&self) -> bool {
matches!(self, IndexedResult::Materialized(_))
}
}
#[allow(dead_code)]
#[derive(Debug)]
pub struct LazyRow<'a, 'b> {
result: &'b IndexedResult<'a>,
row_idx: usize,
}
#[allow(dead_code)]
impl<'a, 'b> LazyRow<'a, 'b> {
pub fn new(result: &'b IndexedResult<'a>, row_idx: usize) -> Self {
Self { result, row_idx }
}
pub fn index(&self) -> usize {
self.row_idx
}
pub fn materialize(&self) -> Option<Vec<Value>> {
match self.result {
IndexedResult::Single(rs) => rs.materialize_row(self.row_idx),
IndexedResult::Joined(js) => {
let left_cols = js.sources().first().map(|t| t.column_count()).unwrap_or(0);
let right_cols = js.sources().get(1).map(|t| t.column_count()).unwrap_or(0);
js.materialize_row(self.row_idx, left_cols, right_cols)
}
IndexedResult::Materialized(rows) => rows.get(self.row_idx).cloned(),
}
}
}
#[allow(dead_code)]
#[derive(Debug)]
pub struct IndexedResultBuilder<'a> {
sources: Vec<&'a Table>,
single_indices: Vec<usize>,
join_indices: Vec<Vec<Option<usize>>>,
is_join: bool,
}
#[allow(dead_code)]
impl<'a> IndexedResultBuilder<'a> {
pub fn new() -> Self {
Self {
sources: Vec::new(),
single_indices: Vec::new(),
join_indices: Vec::new(),
is_join: false,
}
}
pub fn add_source(&mut self, table: &'a Table) {
self.sources.push(table);
if self.sources.len() > 1 {
self.is_join = true;
}
}
pub fn add_row_single(&mut self, row_idx: usize) {
self.single_indices.push(row_idx);
}
pub fn add_row_joined(&mut self, indices: Vec<Option<usize>>) {
self.join_indices.push(indices);
}
pub fn is_empty(&self) -> bool {
self.single_indices.is_empty() && self.join_indices.is_empty()
}
pub fn len(&self) -> usize {
if self.is_join {
self.join_indices.len()
} else {
self.single_indices.len()
}
}
pub fn clear(&mut self) {
self.single_indices.clear();
self.join_indices.clear();
}
pub fn build(self) -> Option<IndexedResult<'a>> {
if self.sources.is_empty() {
return None;
}
if self.is_join && self.sources.len() >= 2 {
let joined = JoinedRowSet {
sources: self.sources,
row_pairs: self.join_indices,
};
Some(IndexedResult::Joined(joined))
} else if let Some(source) = self.sources.into_iter().next() {
let row_set = RowSet {
source,
indices: self.single_indices,
};
Some(IndexedResult::Single(row_set))
} else {
None
}
}
}
impl<'a> Default for IndexedResultBuilder<'a> {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct Table {
name: String,
cols: Vec<Column>,
column_map: HashMap<String, usize>,
storage: Storage,
file_path: Option<PathBuf>,
modified: bool,
delimiter: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DataType {
Integer,
Float,
Text,
Boolean,
}
impl fmt::Display for DataType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DataType::Integer => write!(f, "INTEGER"),
DataType::Float => write!(f, "REAL"),
DataType::Text => write!(f, "TEXT"),
DataType::Boolean => write!(f, "BOOLEAN"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Column {
pub name: String,
pub data_type: DataType,
}
impl fmt::Display for Column {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} ({})", self.name, self.data_type)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ColumnDefinition {
pub name: String,
pub data_type: DataType,
}
impl Table {
pub fn new(name: &str, column_names: Vec<String>, file_path: Option<PathBuf>) -> Self {
let column_map = column_names
.iter()
.enumerate()
.map(|(i, name)| (name.clone(), i))
.collect();
let cols = column_names
.iter()
.map(|name| Column {
name: name.clone(),
data_type: DataType::Text,
})
.collect();
Table {
name: name.to_string(),
cols,
column_map,
storage: Storage::new_memory(),
file_path,
modified: false,
delimiter: ",".to_string(), }
}
pub fn new_with_delimiter(
name: &str,
column_names: Vec<String>,
file_path: Option<PathBuf>,
delimiter: String,
) -> Self {
let mut table = Self::new(name, column_names, file_path);
table.delimiter = delimiter;
table
}
pub fn with_storage(
name: &str,
column_names: Vec<String>,
file_path: Option<PathBuf>,
delimiter: String,
storage: Storage,
) -> Self {
let column_map = column_names
.iter()
.enumerate()
.map(|(i, name)| (name.clone(), i))
.collect();
let cols = column_names
.iter()
.map(|name| Column {
name: name.clone(),
data_type: DataType::Text,
})
.collect();
Table {
name: name.to_string(),
cols,
column_map,
storage,
file_path,
modified: false,
delimiter,
}
}
pub fn new_with_schema(
name: &str,
schema: Vec<ColumnDefinition>,
file_path: Option<PathBuf>,
delimiter: Option<String>,
) -> Self {
let columns: Vec<String> = schema.iter().map(|col_def| col_def.name.clone()).collect();
let cols = schema
.iter()
.map(|col_def| Column {
name: col_def.name.clone(),
data_type: col_def.data_type,
})
.collect();
let column_map = columns
.iter()
.enumerate()
.map(|(i, name)| (name.clone(), i))
.collect();
Table {
name: name.to_string(),
cols,
column_map,
storage: Storage::new_memory(),
file_path,
modified: true, delimiter: delimiter.unwrap_or_else(|| ",".to_string()),
}
}
pub fn columns(&self) -> Vec<String> {
self.cols.iter().map(|col| col.name.clone()).collect()
}
pub fn column_metadata(&self) -> &[Column] {
&self.cols
}
pub fn column_count(&self) -> usize {
self.columns().len()
}
pub fn rows(&self) -> &[Row] {
self.storage.rows()
}
pub fn set_name(&mut self, name: String) {
self.name = name;
}
pub fn name(&self) -> &str {
&self.name
}
pub fn row_count(&self) -> usize {
self.storage.row_count()
}
pub fn add_row(&mut self, row: Row) -> SqawkResult<()> {
if row.len() != self.column_count() {
return Err(SqawkError::InvalidSqlQuery(format!(
"Row has {} columns, but table '{}' has {} columns",
row.len(),
self.name,
self.column_count()
)));
}
self.storage.ensure_mutable();
self.storage.push_row(row);
self.modified = true;
Ok(())
}
pub fn add_row_from_slice(&mut self, row: &[Value]) -> SqawkResult<()> {
if row.len() != self.column_count() {
return Err(SqawkError::InvalidSqlQuery(format!(
"Row has {} columns, but table '{}' has {} columns",
row.len(),
self.name,
self.column_count()
)));
}
self.storage.ensure_mutable();
self.storage.push_row(row.to_vec());
self.modified = true;
Ok(())
}
pub fn file_path(&self) -> Option<&PathBuf> {
self.file_path.as_ref()
}
pub fn detach_file_path(&mut self) {
self.file_path = None;
}
pub fn delimiter(&self) -> &String {
&self.delimiter
}
pub fn set_delimiter(&mut self, delimiter: String) {
self.delimiter = delimiter;
}
pub fn column_index(&self, name: &str) -> Option<usize> {
self.column_map.get(name).copied()
}
pub fn print_to_stdout(&self) -> Result<()> {
let delim = &self.delimiter;
let column_names = self.columns();
for (i, col) in column_names.iter().enumerate() {
if i > 0 {
print!("{}", delim);
}
print!("{}", col);
}
println!();
for row in self.rows() {
for (i, val) in row.iter().enumerate() {
if i > 0 {
print!("{}", delim);
}
print!("{}", val);
}
println!();
}
Ok(())
}
pub fn replace_rows(&mut self, new_rows: Vec<Row>) {
self.storage.ensure_mutable();
self.storage.replace_rows(new_rows);
self.modified = true;
}
pub fn replace_row(&mut self, row_index: usize, row: Row) -> SqawkResult<()> {
self.storage.ensure_mutable();
let rows = self.storage.rows_mut().ok_or_else(|| {
SqawkError::InvalidSqlQuery("Table storage is not mutable".to_string())
})?;
if row_index >= rows.len() {
return Err(SqawkError::InvalidSqlQuery(format!(
"Row index {} out of range for table '{}'",
row_index, self.name
)));
}
rows[row_index] = row;
self.modified = true;
Ok(())
}
#[cfg(test)]
pub fn add_column(&mut self, name: String, data_type_str: String) {
let data_type = match data_type_str.to_uppercase().as_str() {
"INT" | "INTEGER" => DataType::Integer,
"FLOAT" | "REAL" | "DOUBLE" => DataType::Float,
"BOOL" | "BOOLEAN" => DataType::Boolean,
_ => DataType::Text,
};
let column = Column {
name: name.clone(),
data_type,
};
self.cols.push(column);
let new_index = self.cols.len() - 1;
self.column_map.insert(name, new_index);
self.storage.ensure_mutable();
self.modified = true;
}
pub fn add_column_with_default(
&mut self,
name: String,
data_type: DataType,
default_value: Value,
) -> crate::error::SqawkResult<()> {
let column = Column {
name: name.clone(),
data_type,
};
self.cols.push(column);
let new_index = self.cols.len() - 1;
self.column_map.insert(name, new_index);
self.storage.ensure_mutable();
if let Some(rows) = self.storage.rows_mut() {
for row in rows {
row.push(default_value.clone());
}
}
self.modified = true;
Ok(())
}
pub fn clear_rows(&mut self) -> crate::error::SqawkResult<()> {
self.storage.ensure_mutable();
self.storage.replace_rows(Vec::new());
self.modified = true;
Ok(())
}
pub fn rows_as_strings(&self) -> Vec<Vec<String>> {
self.rows()
.iter()
.map(|row| row.iter().map(|value| value.to_string()).collect())
.collect()
}
}