mod engine;
mod eval;
use std::collections::HashMap;
use std::fmt;
use std::hash::{Hash, Hasher};
use crate::ast::Statement;
use crate::dialects::Dialect;
use crate::errors::Result;
use crate::parser;
#[derive(Debug, Clone)]
pub enum Value {
Null,
Boolean(bool),
Int(i64),
Float(f64),
String(String),
}
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Value::Null, Value::Null) => true,
(Value::Boolean(a), Value::Boolean(b)) => a == b,
(Value::Int(a), Value::Int(b)) => a == b,
(Value::Float(a), Value::Float(b)) => a == b,
(Value::Int(a), Value::Float(b)) => (*a as f64) == *b,
(Value::Float(a), Value::Int(b)) => *a == (*b as f64),
(Value::String(a), Value::String(b)) => a == b,
_ => false,
}
}
}
impl Eq for Value {}
impl Hash for Value {
fn hash<H: Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
match self {
Value::Null => {}
Value::Boolean(b) => b.hash(state),
Value::Int(i) => i.hash(state),
Value::Float(f) => f.to_bits().hash(state),
Value::String(s) => s.hash(state),
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Null => write!(f, "NULL"),
Value::Boolean(b) => write!(f, "{b}"),
Value::Int(i) => write!(f, "{i}"),
Value::Float(v) => {
if v.fract() == 0.0 && v.abs() < 1e15 {
write!(f, "{v:.1}")
} else {
write!(f, "{v}")
}
}
Value::String(s) => write!(f, "{s}"),
}
}
}
impl Value {
#[must_use]
pub fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
#[must_use]
pub fn is_truthy(&self) -> bool {
match self {
Value::Null => false,
Value::Boolean(b) => *b,
Value::Int(i) => *i != 0,
Value::Float(f) => *f != 0.0,
Value::String(s) => !s.is_empty(),
}
}
#[must_use]
pub fn to_f64(&self) -> Option<f64> {
match self {
Value::Int(i) => Some(*i as f64),
Value::Float(f) => Some(*f),
Value::String(s) => s.parse().ok(),
Value::Boolean(b) => Some(if *b { 1.0 } else { 0.0 }),
Value::Null => None,
}
}
#[must_use]
pub fn to_i64(&self) -> Option<i64> {
match self {
Value::Int(i) => Some(*i),
Value::Float(f) => Some(*f as i64),
Value::String(s) => s.parse().ok(),
Value::Boolean(b) => Some(i64::from(*b)),
Value::Null => None,
}
}
#[must_use]
pub fn to_string_val(&self) -> String {
match self {
Value::Null => String::new(),
other => other.to_string(),
}
}
}
impl PartialOrd for Value {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match (self, other) {
(Value::Null, Value::Null) => Some(std::cmp::Ordering::Equal),
(Value::Null, _) => Some(std::cmp::Ordering::Less),
(_, Value::Null) => Some(std::cmp::Ordering::Greater),
(Value::Int(a), Value::Int(b)) => a.partial_cmp(b),
(Value::Float(a), Value::Float(b)) => a.partial_cmp(b),
(Value::Int(a), Value::Float(b)) => (*a as f64).partial_cmp(b),
(Value::Float(a), Value::Int(b)) => a.partial_cmp(&(*b as f64)),
(Value::String(a), Value::String(b)) => Some(a.cmp(b)),
(Value::Boolean(a), Value::Boolean(b)) => a.partial_cmp(b),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct Table {
pub columns: Vec<String>,
pub rows: Vec<Vec<Value>>,
}
impl Table {
pub fn new(columns: Vec<String>, rows: Vec<Vec<Value>>) -> Self {
Self { columns, rows }
}
pub fn from_rows(columns: Vec<&str>, rows: Vec<Vec<Value>>) -> Self {
Self {
columns: columns.into_iter().map(String::from).collect(),
rows,
}
}
}
pub type Tables = HashMap<String, Table>;
#[derive(Debug, Clone)]
pub struct ResultSet {
pub columns: Vec<String>,
pub rows: Vec<Vec<Value>>,
}
impl ResultSet {
#[must_use]
pub fn new(columns: Vec<String>, rows: Vec<Vec<Value>>) -> Self {
Self { columns, rows }
}
#[must_use]
pub fn empty() -> Self {
Self {
columns: vec![],
rows: vec![],
}
}
#[must_use]
pub fn row_count(&self) -> usize {
self.rows.len()
}
#[must_use]
pub fn column_count(&self) -> usize {
self.columns.len()
}
}
impl fmt::Display for ResultSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.columns.is_empty() {
return write!(f, "(empty)");
}
let mut widths: Vec<usize> = self.columns.iter().map(|c| c.len()).collect();
for row in &self.rows {
for (i, val) in row.iter().enumerate() {
if i < widths.len() {
widths[i] = widths[i].max(val.to_string().len());
}
}
}
let header: Vec<String> = self
.columns
.iter()
.enumerate()
.map(|(i, c)| format!("{:width$}", c, width = widths[i]))
.collect();
writeln!(f, "{}", header.join(" | "))?;
let sep: Vec<String> = widths.iter().map(|w| "-".repeat(*w)).collect();
writeln!(f, "{}", sep.join("-+-"))?;
for row in &self.rows {
let cells: Vec<String> = row
.iter()
.enumerate()
.map(|(i, v)| format!("{:width$}", v, width = widths.get(i).copied().unwrap_or(0)))
.collect();
writeln!(f, "{}", cells.join(" | "))?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub(crate) struct RowContext {
pub columns: Vec<String>,
pub values: Vec<Value>,
}
impl RowContext {
pub fn empty() -> Self {
Self {
columns: vec![],
values: vec![],
}
}
pub fn new(columns: Vec<String>, values: Vec<Value>) -> Self {
Self { columns, values }
}
pub fn get(&self, name: &str) -> Option<&Value> {
let name_lower = name.to_lowercase();
for (i, col) in self.columns.iter().enumerate() {
if col.to_lowercase() == name_lower {
return Some(&self.values[i]);
}
}
for (i, col) in self.columns.iter().enumerate() {
let col_lower = col.to_lowercase();
if let Some((_, suffix)) = col_lower.rsplit_once('.') {
if suffix == name_lower {
return Some(&self.values[i]);
}
}
}
None
}
pub fn get_qualified(&self, table: &str, name: &str) -> Option<&Value> {
let qualified = format!("{}.{}", table, name).to_lowercase();
for (i, col) in self.columns.iter().enumerate() {
if col.to_lowercase() == qualified {
return Some(&self.values[i]);
}
}
self.get(name)
}
pub fn merge(&self, other: &RowContext) -> RowContext {
let mut columns = self.columns.clone();
let mut values = self.values.clone();
columns.extend(other.columns.iter().cloned());
values.extend(other.values.iter().cloned());
RowContext { columns, values }
}
pub fn null_row(columns: &[String]) -> RowContext {
RowContext {
columns: columns.to_vec(),
values: vec![Value::Null; columns.len()],
}
}
}
pub fn execute(sql: &str, tables: &Tables) -> Result<ResultSet> {
let stmt = parser::parse(sql, Dialect::Ansi)?;
execute_statement(&stmt, tables)
}
pub fn execute_statement(stmt: &Statement, tables: &Tables) -> Result<ResultSet> {
let mut ctx = engine::ExecutionContext::new(tables);
ctx.execute(stmt)
}