pub const SQLPARSER_VERSION: &str = "0.62";
pub mod ast_compat;
pub mod bytecode;
pub mod compiler;
mod compiler_aggregate;
mod compiler_ddl;
mod compiler_dml;
mod compiler_join;
mod compiler_window;
pub mod engine;
#[cfg(test)]
mod compiler_tests;
#[cfg(test)]
mod tests;
use std::collections::HashSet;
use crate::capacity::DEFAULT_TABLE_CAPACITY;
use crate::database::Database;
use crate::error::{SqawkError, SqawkResult};
use crate::table::Table;
pub struct VmExecutionResult {
pub tables: Vec<Table>,
pub modified_tables: HashSet<String>,
pub affected_rows: usize,
pub dml_executed: bool,
}
pub fn execute_vm(
sql: &str,
database: &mut Database,
verbose: bool,
) -> SqawkResult<VmExecutionResult> {
if verbose {
println!("VM Engine: Executing SQL via bytecode: {}", sql);
let sql_upper = sql.to_uppercase();
if sql_upper.contains("DISTINCT") {
eprintln!("Applying DISTINCT");
}
if sql_upper.contains("ORDER BY") {
eprintln!("Applying ORDER BY");
}
if sql_upper.contains("LIMIT") || sql_upper.contains("OFFSET") {
eprintln!("Applying LIMIT/OFFSET");
}
if sql_upper.contains("GROUP BY") {
eprintln!("Applying GROUP BY");
}
if sql_upper.contains("HAVING") {
eprintln!("Applying HAVING");
}
}
let dialect = sqlparser::dialect::HiveDialect {};
let statements =
sqlparser::parser::Parser::parse_sql(&dialect, sql).map_err(SqawkError::SqlParseError)?;
if statements.is_empty() {
return Err(SqawkError::InvalidSqlQuery(
"No SQL statements found".to_string(),
));
}
let mut tables: Vec<Table> = Vec::new();
let mut modified_tables = HashSet::with_capacity(DEFAULT_TABLE_CAPACITY);
let mut affected_rows: usize = 0;
let mut dml_executed = false;
for statement in &statements {
let mut statement = statement.clone();
let derived = materialize_derived_tables(&mut statement, database, verbose)?;
let outcome = (|| -> SqawkResult<(Option<Table>, AppliedModifications)> {
let program = {
let mut compiler = compiler::SqlCompiler::new(database, verbose);
compiler.compile_statement_program(&statement)?
};
if verbose {
println!("Generated bytecode:");
println!("{}", program);
}
let mut vm = engine::VmEngine::new_mut(database, verbose);
vm.init(program);
vm.execute()?;
let result_table = vm.create_result_table()?;
let modifications = vm.take_modifications();
drop(vm);
let applied = apply_modifications(database, modifications, verbose)?;
Ok((result_table, applied))
})();
for name in &derived {
database.remove_table(name);
}
let (result_table, applied) = outcome?;
modified_tables.extend(applied.modified_tables);
if is_row_counting_dml(&statement) {
affected_rows = applied.affected_rows;
dml_executed = true;
}
if let Some(t) = result_table {
tables.push(t);
}
}
Ok(VmExecutionResult {
tables,
modified_tables,
affected_rows,
dml_executed,
})
}
fn materialize_derived_tables(
statement: &mut sqlparser::ast::Statement,
database: &mut Database,
verbose: bool,
) -> SqawkResult<Vec<String>> {
let mut registered = Vec::new();
if let sqlparser::ast::Statement::Query(query) = statement {
materialize_in_query(query, database, verbose, &mut registered)?;
}
Ok(registered)
}
fn materialize_in_query(
query: &mut sqlparser::ast::Query,
database: &mut Database,
verbose: bool,
registered: &mut Vec<String>,
) -> SqawkResult<()> {
if let sqlparser::ast::SetExpr::Select(select) = &mut *query.body {
for twj in &mut select.from {
materialize_in_factor(&mut twj.relation, database, verbose, registered)?;
for join in &mut twj.joins {
materialize_in_factor(&mut join.relation, database, verbose, registered)?;
}
}
}
Ok(())
}
fn materialize_in_factor(
factor: &mut sqlparser::ast::TableFactor,
database: &mut Database,
verbose: bool,
registered: &mut Vec<String>,
) -> SqawkResult<()> {
let (subquery, alias) = match factor {
sqlparser::ast::TableFactor::Derived {
subquery, alias, ..
} => (subquery.clone(), alias.clone()),
_ => return Ok(()),
};
let alias = alias.ok_or_else(|| {
SqawkError::InvalidSqlQuery("Subquery in FROM must have an alias".to_string())
})?;
let name = alias.name.value.to_ascii_lowercase();
if database.has_table(&name) {
return Err(SqawkError::InvalidSqlQuery(format!(
"Derived table alias '{}' shadows an existing table",
name
)));
}
let mut inner = *subquery;
materialize_in_query(&mut inner, database, verbose, registered)?;
let inner_sql = inner.to_string();
let result = execute_vm(&inner_sql, database, verbose)?;
let mut table = result.tables.into_iter().next_back().ok_or_else(|| {
SqawkError::InvalidSqlQuery("Subquery in FROM produced no result".to_string())
})?;
table.set_name(name.clone());
database.add_table(name.clone(), table)?;
registered.push(name.clone());
*factor = sqlparser::ast::TableFactor::Table {
name: sqlparser::ast::ObjectName(vec![sqlparser::ast::ObjectNamePart::Identifier(
sqlparser::ast::Ident::new(name),
)]),
alias: None,
args: None,
with_hints: Vec::new(),
version: None,
partitions: Vec::new(),
with_ordinality: false,
json_path: None,
sample: None,
index_hints: Vec::new(),
};
Ok(())
}
fn is_row_counting_dml(statement: &sqlparser::ast::Statement) -> bool {
use sqlparser::ast::Statement;
matches!(
statement,
Statement::Insert(_)
| Statement::Update(_)
| Statement::Delete(_)
| Statement::Truncate(_)
| Statement::CreateTable(_)
)
}
struct AppliedModifications {
modified_tables: HashSet<String>,
affected_rows: usize,
}
fn apply_modifications(
database: &mut Database,
modifications: Vec<engine::TableModification>,
verbose: bool,
) -> SqawkResult<AppliedModifications> {
let mut modified_tables = HashSet::with_capacity(DEFAULT_TABLE_CAPACITY);
let mut affected_rows: usize = 0;
let mut deletions_by_table: std::collections::HashMap<String, HashSet<usize>> =
std::collections::HashMap::new();
let mut insert_counts_by_table: std::collections::HashMap<String, usize> =
std::collections::HashMap::new();
let mut replace_counts_by_table: std::collections::HashMap<String, usize> =
std::collections::HashMap::new();
for modification in modifications {
match modification {
engine::TableModification::Insert { table_name, row } => {
let table = database.get_table_mut(&table_name)?;
table.add_row(row)?;
*insert_counts_by_table
.entry(table_name.clone())
.or_insert(0) += 1;
modified_tables.insert(table_name);
}
engine::TableModification::Replace {
table_name,
row_index,
row,
} => {
let table = database.get_table_mut(&table_name)?;
table.replace_row(row_index, row)?;
*replace_counts_by_table
.entry(table_name.clone())
.or_insert(0) += 1;
modified_tables.insert(table_name);
}
engine::TableModification::Delete {
table_name,
row_index,
} => {
deletions_by_table
.entry(table_name)
.or_default()
.insert(row_index);
}
engine::TableModification::CreateTable {
table_name,
columns,
file_path,
delimiter,
} => {
let schema: Vec<crate::table::ColumnDefinition> = columns
.into_iter()
.map(|col| crate::table::ColumnDefinition {
name: col.name,
data_type: match col.data_type.as_str() {
"INTEGER" => crate::table::DataType::Integer,
"REAL" => crate::table::DataType::Float,
"BOOLEAN" => crate::table::DataType::Boolean,
_ => crate::table::DataType::Text,
},
})
.collect();
let file_path_buf = file_path.map(std::path::PathBuf::from);
let table = crate::table::Table::new_with_schema(
&table_name,
schema,
file_path_buf,
delimiter,
);
database.add_table(table_name.clone(), table)?;
modified_tables.insert(table_name);
}
engine::TableModification::DropTable { table_name } => {
if !database.remove_table(&table_name) {
return Err(crate::error::SqawkError::TableNotFound(table_name));
}
}
engine::TableModification::AlterTableAddColumn {
table_name,
column_name,
column_type,
} => {
let table = database.get_table_mut(&table_name)?;
let data_type = match column_type.as_str() {
"INTEGER" => crate::table::DataType::Integer,
"REAL" => crate::table::DataType::Float,
"BOOLEAN" => crate::table::DataType::Boolean,
_ => crate::table::DataType::Text,
};
table.add_column_with_default(column_name, data_type, crate::table::Value::Null)?;
modified_tables.insert(table_name);
}
engine::TableModification::Truncate { table_name } => {
let table = database.get_table_mut(&table_name)?;
let row_count = table.row_count();
table.clear_rows()?;
affected_rows += row_count;
modified_tables.insert(table_name);
}
}
}
for (table_name, insert_count) in &insert_counts_by_table {
let delete_count = deletions_by_table
.get(table_name)
.map(|s| s.len())
.unwrap_or(0);
if delete_count == 0 {
affected_rows += insert_count;
}
}
for (table_name, indices_to_delete) in deletions_by_table {
let delete_count = indices_to_delete.len();
let table = database.get_table_mut(&table_name)?;
let new_rows: Vec<crate::table::Row> = table
.rows()
.iter()
.enumerate()
.filter(|(idx, _)| !indices_to_delete.contains(idx))
.map(|(_, row)| {
row.iter()
.map(|value| match value {
crate::table::Value::String(cow) => {
crate::table::Value::String(std::borrow::Cow::Owned(cow.to_string()))
}
v => v.clone(),
})
.collect()
})
.collect();
table.replace_rows(new_rows);
modified_tables.insert(table_name.clone());
let insert_count = insert_counts_by_table
.get(&table_name)
.copied()
.unwrap_or(0);
if insert_count == delete_count && insert_count > 0 {
affected_rows += delete_count;
if verbose {
eprintln!("Updated {} rows", delete_count);
}
} else {
affected_rows += delete_count;
if verbose {
eprintln!("Deleted {} rows", delete_count);
}
}
}
for (_table, count) in replace_counts_by_table {
affected_rows += count;
if verbose {
eprintln!("Updated {} rows", count);
}
}
Ok(AppliedModifications {
modified_tables,
affected_rows,
})
}