copy generation code from simulator

This commit is contained in:
pedrocarlo
2025-08-25 15:14:10 -03:00
parent b16f96b507
commit 0285bdd72c
25 changed files with 6490 additions and 3 deletions

View File

@@ -0,0 +1,4 @@
pub mod query;
pub mod table;
pub(crate) const FAULT_ERROR_MSG: &str = "Injected fault";

View File

@@ -0,0 +1,45 @@
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::{
generation::Shadow,
model::table::{SimValue, Table},
runner::env::SimulatorTables,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct Create {
pub(crate) table: Table,
}
impl Shadow for Create {
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
if !tables.iter().any(|t| t.name == self.table.name) {
tables.push(self.table.clone());
Ok(vec![])
} else {
Err(anyhow::anyhow!(
"Table {} already exists. CREATE TABLE statement ignored.",
self.table.name
))
}
}
}
impl Display for Create {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "CREATE TABLE {} (", self.table.name)?;
for (i, column) in self.table.columns.iter().enumerate() {
if i != 0 {
write!(f, ",")?;
}
write!(f, "{} {}", column.name, column.column_type)?;
}
write!(f, ")")
}
}

View File

@@ -0,0 +1,106 @@
use crate::{
generation::{gen_random_text, pick, pick_n_unique, ArbitraryFrom, Shadow},
model::table::SimValue,
runner::env::{SimulatorEnv, SimulatorTables},
};
use rand::Rng;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum SortOrder {
Asc,
Desc,
}
impl std::fmt::Display for SortOrder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SortOrder::Asc => write!(f, "ASC"),
SortOrder::Desc => write!(f, "DESC"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub(crate) struct CreateIndex {
pub(crate) index_name: String,
pub(crate) table_name: String,
pub(crate) columns: Vec<(String, SortOrder)>,
}
impl Shadow for CreateIndex {
type Result = Vec<Vec<SimValue>>;
fn shadow(&self, env: &mut SimulatorTables) -> Vec<Vec<SimValue>> {
env.tables
.iter_mut()
.find(|t| t.name == self.table_name)
.unwrap()
.indexes
.push(self.index_name.clone());
vec![]
}
}
impl std::fmt::Display for CreateIndex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"CREATE INDEX {} ON {} ({})",
self.index_name,
self.table_name,
self.columns
.iter()
.map(|(name, order)| format!("{name} {order}"))
.collect::<Vec<String>>()
.join(", ")
)
}
}
impl ArbitraryFrom<&SimulatorEnv> for CreateIndex {
fn arbitrary_from<R: Rng>(rng: &mut R, env: &SimulatorEnv) -> Self {
assert!(
!env.tables.is_empty(),
"Cannot create an index when no tables exist in the environment."
);
let table = pick(&env.tables, rng);
if table.columns.is_empty() {
panic!(
"Cannot create an index on table '{}' as it has no columns.",
table.name
);
}
let num_columns_to_pick = rng.random_range(1..=table.columns.len());
let picked_column_indices = pick_n_unique(0..table.columns.len(), num_columns_to_pick, rng);
let columns = picked_column_indices
.into_iter()
.map(|i| {
let column = &table.columns[i];
(
column.name.clone(),
if rng.random_bool(0.5) {
SortOrder::Asc
} else {
SortOrder::Desc
},
)
})
.collect::<Vec<(String, SortOrder)>>();
let index_name = format!(
"idx_{}_{}",
table.name,
gen_random_text(rng).chars().take(8).collect::<String>()
);
CreateIndex {
index_name,
table_name: table.name.clone(),
columns,
}
}
}

View File

@@ -0,0 +1,41 @@
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::{generation::Shadow, model::table::SimValue, runner::env::SimulatorTables};
use super::predicate::Predicate;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub(crate) struct Delete {
pub(crate) table: String,
pub(crate) predicate: Predicate,
}
impl Shadow for Delete {
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
let table = tables.tables.iter_mut().find(|t| t.name == self.table);
if let Some(table) = table {
// If the table exists, we can delete from it
let t2 = table.clone();
table.rows.retain_mut(|r| !self.predicate.test(r, &t2));
} else {
// If the table does not exist, we return an error
return Err(anyhow::anyhow!(
"Table {} does not exist. DELETE statement ignored.",
self.table
));
}
Ok(vec![])
}
}
impl Display for Delete {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "DELETE FROM {} WHERE {}", self.table, self.predicate)
}
}

View File

@@ -0,0 +1,34 @@
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::{generation::Shadow, model::table::SimValue, runner::env::SimulatorTables};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub(crate) struct Drop {
pub(crate) table: String,
}
impl Shadow for Drop {
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
if !tables.iter().any(|t| t.name == self.table) {
// If the table does not exist, we return an error
return Err(anyhow::anyhow!(
"Table {} does not exist. DROP statement ignored.",
self.table
));
}
tables.tables.retain(|t| t.name != self.table);
Ok(vec![])
}
}
impl Display for Drop {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "DROP TABLE {}", self.table)
}
}

View File

@@ -0,0 +1,87 @@
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::{generation::Shadow, model::table::SimValue, runner::env::SimulatorTables};
use super::select::Select;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub(crate) enum Insert {
Values {
table: String,
values: Vec<Vec<SimValue>>,
},
Select {
table: String,
select: Box<Select>,
},
}
impl Shadow for Insert {
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
match self {
Insert::Values { table, values } => {
if let Some(t) = tables.tables.iter_mut().find(|t| &t.name == table) {
t.rows.extend(values.clone());
} else {
return Err(anyhow::anyhow!(
"Table {} does not exist. INSERT statement ignored.",
table
));
}
}
Insert::Select { table, select } => {
let rows = select.shadow(tables)?;
if let Some(t) = tables.tables.iter_mut().find(|t| &t.name == table) {
t.rows.extend(rows);
} else {
return Err(anyhow::anyhow!(
"Table {} does not exist. INSERT statement ignored.",
table
));
}
}
}
Ok(vec![])
}
}
impl Insert {
pub(crate) fn table(&self) -> &str {
match self {
Insert::Values { table, .. } | Insert::Select { table, .. } => table,
}
}
}
impl Display for Insert {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Insert::Values { table, values } => {
write!(f, "INSERT INTO {table} VALUES ")?;
for (i, row) in values.iter().enumerate() {
if i != 0 {
write!(f, ", ")?;
}
write!(f, "(")?;
for (j, value) in row.iter().enumerate() {
if j != 0 {
write!(f, ", ")?;
}
write!(f, "{value}")?;
}
write!(f, ")")?;
}
Ok(())
}
Insert::Select { table, select } => {
write!(f, "INSERT INTO {table} ")?;
write!(f, "{select}")
}
}
}
}

View File

@@ -0,0 +1,129 @@
use std::{collections::HashSet, fmt::Display};
pub(crate) use create::Create;
pub(crate) use create_index::CreateIndex;
pub(crate) use delete::Delete;
pub(crate) use drop::Drop;
pub(crate) use insert::Insert;
pub(crate) use select::Select;
use serde::{Deserialize, Serialize};
use turso_parser::ast::fmt::ToSqlContext;
use update::Update;
use crate::{
generation::Shadow,
model::{
query::transaction::{Begin, Commit, Rollback},
table::SimValue,
},
runner::env::SimulatorTables,
};
pub mod create;
pub mod create_index;
pub mod delete;
pub mod drop;
pub mod insert;
pub mod predicate;
pub mod select;
pub mod transaction;
pub mod update;
// This type represents the potential queries on the database.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) enum Query {
Create(Create),
Select(Select),
Insert(Insert),
Delete(Delete),
Update(Update),
Drop(Drop),
CreateIndex(CreateIndex),
Begin(Begin),
Commit(Commit),
Rollback(Rollback),
}
impl Query {
pub(crate) fn dependencies(&self) -> HashSet<String> {
match self {
Query::Select(select) => select.dependencies(),
Query::Create(_) => HashSet::new(),
Query::Insert(Insert::Select { table, .. })
| Query::Insert(Insert::Values { table, .. })
| Query::Delete(Delete { table, .. })
| Query::Update(Update { table, .. })
| Query::Drop(Drop { table, .. }) => HashSet::from_iter([table.clone()]),
Query::CreateIndex(CreateIndex { table_name, .. }) => {
HashSet::from_iter([table_name.clone()])
}
Query::Begin(_) | Query::Commit(_) | Query::Rollback(_) => HashSet::new(),
}
}
pub(crate) fn uses(&self) -> Vec<String> {
match self {
Query::Create(Create { table }) => vec![table.name.clone()],
Query::Select(select) => select.dependencies().into_iter().collect(),
Query::Insert(Insert::Select { table, .. })
| Query::Insert(Insert::Values { table, .. })
| Query::Delete(Delete { table, .. })
| Query::Update(Update { table, .. })
| Query::Drop(Drop { table, .. }) => vec![table.clone()],
Query::CreateIndex(CreateIndex { table_name, .. }) => vec![table_name.clone()],
Query::Begin(..) | Query::Commit(..) | Query::Rollback(..) => vec![],
}
}
}
impl Shadow for Query {
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
fn shadow(&self, env: &mut SimulatorTables) -> Self::Result {
match self {
Query::Create(create) => create.shadow(env),
Query::Insert(insert) => insert.shadow(env),
Query::Delete(delete) => delete.shadow(env),
Query::Select(select) => select.shadow(env),
Query::Update(update) => update.shadow(env),
Query::Drop(drop) => drop.shadow(env),
Query::CreateIndex(create_index) => Ok(create_index.shadow(env)),
Query::Begin(begin) => Ok(begin.shadow(env)),
Query::Commit(commit) => Ok(commit.shadow(env)),
Query::Rollback(rollback) => Ok(rollback.shadow(env)),
}
}
}
impl Display for Query {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Create(create) => write!(f, "{create}"),
Self::Select(select) => write!(f, "{select}"),
Self::Insert(insert) => write!(f, "{insert}"),
Self::Delete(delete) => write!(f, "{delete}"),
Self::Update(update) => write!(f, "{update}"),
Self::Drop(drop) => write!(f, "{drop}"),
Self::CreateIndex(create_index) => write!(f, "{create_index}"),
Self::Begin(begin) => write!(f, "{begin}"),
Self::Commit(commit) => write!(f, "{commit}"),
Self::Rollback(rollback) => write!(f, "{rollback}"),
}
}
}
/// Used to print sql strings that already have all the context it needs
pub(crate) struct EmptyContext;
impl ToSqlContext for EmptyContext {
fn get_column_name(
&self,
_table_id: turso_parser::ast::TableInternalId,
_col_idx: usize,
) -> String {
unreachable!()
}
fn get_table_name(&self, _id: turso_parser::ast::TableInternalId) -> &str {
unreachable!()
}
}

View File

@@ -0,0 +1,146 @@
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use turso_parser::ast::{self, fmt::ToTokens};
use crate::model::table::{SimValue, Table, TableContext};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Predicate(pub ast::Expr);
impl Predicate {
pub(crate) fn true_() -> Self {
Self(ast::Expr::Literal(ast::Literal::Keyword(
"TRUE".to_string(),
)))
}
pub(crate) fn false_() -> Self {
Self(ast::Expr::Literal(ast::Literal::Keyword(
"FALSE".to_string(),
)))
}
pub(crate) fn null() -> Self {
Self(ast::Expr::Literal(ast::Literal::Null))
}
pub(crate) fn not(predicate: Predicate) -> Self {
let expr = ast::Expr::Unary(ast::UnaryOperator::Not, Box::new(predicate.0));
Self(expr).parens()
}
pub(crate) fn and(predicates: Vec<Predicate>) -> Self {
if predicates.is_empty() {
Self::true_()
} else if predicates.len() == 1 {
predicates.into_iter().next().unwrap().parens()
} else {
let expr = ast::Expr::Binary(
Box::new(predicates[0].0.clone()),
ast::Operator::And,
Box::new(Self::and(predicates[1..].to_vec()).0),
);
Self(expr).parens()
}
}
pub(crate) fn or(predicates: Vec<Predicate>) -> Self {
if predicates.is_empty() {
Self::false_()
} else if predicates.len() == 1 {
predicates.into_iter().next().unwrap().parens()
} else {
let expr = ast::Expr::Binary(
Box::new(predicates[0].0.clone()),
ast::Operator::Or,
Box::new(Self::or(predicates[1..].to_vec()).0),
);
Self(expr).parens()
}
}
pub(crate) fn eq(lhs: Predicate, rhs: Predicate) -> Self {
let expr = ast::Expr::Binary(Box::new(lhs.0), ast::Operator::Equals, Box::new(rhs.0));
Self(expr).parens()
}
pub(crate) fn is(lhs: Predicate, rhs: Predicate) -> Self {
let expr = ast::Expr::Binary(Box::new(lhs.0), ast::Operator::Is, Box::new(rhs.0));
Self(expr).parens()
}
pub(crate) fn parens(self) -> Self {
let expr = ast::Expr::Parenthesized(vec![self.0]);
Self(expr)
}
pub(crate) fn eval(&self, row: &[SimValue], table: &Table) -> Option<SimValue> {
expr_to_value(&self.0, row, table)
}
pub(crate) fn test<T: TableContext>(&self, row: &[SimValue], table: &T) -> bool {
let value = expr_to_value(&self.0, row, table);
value.is_some_and(|value| value.as_bool())
}
}
// TODO: In the future pass a Vec<Table> to support resolving a value from another table
// This function attempts to convert an simpler easily computable expression into values
// TODO: In the future, we can try to expand this computation if we want to support harder properties that require us
// to already know more values before hand
pub fn expr_to_value<T: TableContext>(
expr: &ast::Expr,
row: &[SimValue],
table: &T,
) -> Option<SimValue> {
match expr {
ast::Expr::DoublyQualified(_, _, ast::Name::Ident(col_name))
| ast::Expr::DoublyQualified(_, _, ast::Name::Quoted(col_name))
| ast::Expr::Qualified(_, ast::Name::Ident(col_name))
| ast::Expr::Qualified(_, ast::Name::Quoted(col_name))
| ast::Expr::Id(ast::Name::Ident(col_name)) => {
let columns = table.columns().collect::<Vec<_>>();
assert_eq!(row.len(), columns.len());
columns
.iter()
.zip(row.iter())
.find(|(column, _)| column.column.name == *col_name)
.map(|(_, value)| value)
.cloned()
}
ast::Expr::Literal(literal) => Some(literal.into()),
ast::Expr::Binary(lhs, op, rhs) => {
let lhs = expr_to_value(lhs, row, table)?;
let rhs = expr_to_value(rhs, row, table)?;
Some(lhs.binary_compare(&rhs, *op))
}
ast::Expr::Like {
lhs,
not,
op,
rhs,
escape: _, // TODO: support escape
} => {
let lhs = expr_to_value(lhs, row, table)?;
let rhs = expr_to_value(rhs, row, table)?;
let res = lhs.like_compare(&rhs, *op);
let value: SimValue = if *not { !res } else { res }.into();
Some(value)
}
ast::Expr::Unary(op, expr) => {
let value = expr_to_value(expr, row, table)?;
Some(value.unary_exec(*op))
}
ast::Expr::Parenthesized(exprs) => {
assert_eq!(exprs.len(), 1);
expr_to_value(&exprs[0], row, table)
}
_ => unreachable!("{:?}", expr),
}
}
impl Display for Predicate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.to_fmt(f)
}
}

View File

@@ -0,0 +1,496 @@
use std::{collections::HashSet, fmt::Display};
use anyhow::Context;
pub use ast::Distinctness;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use turso_parser::ast::{self, fmt::ToTokens, SortOrder};
use crate::{
generation::Shadow,
model::{
query::EmptyContext,
table::{JoinTable, JoinType, JoinedTable, SimValue, Table, TableContext},
},
runner::env::SimulatorTables,
};
use super::predicate::Predicate;
/// `SELECT` or `RETURNING` result column
// https://sqlite.org/syntax/result-column.html
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum ResultColumn {
/// expression
Expr(Predicate),
/// `*`
Star,
/// column name
Column(String),
}
impl Display for ResultColumn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ResultColumn::Expr(expr) => write!(f, "({expr})"),
ResultColumn::Star => write!(f, "*"),
ResultColumn::Column(name) => write!(f, "{name}"),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub(crate) struct Select {
pub(crate) body: SelectBody,
pub(crate) limit: Option<usize>,
}
impl Select {
pub fn simple(table: String, where_clause: Predicate) -> Self {
Self::single(
table,
vec![ResultColumn::Star],
where_clause,
None,
Distinctness::All,
)
}
pub fn expr(expr: Predicate) -> Self {
Select {
body: SelectBody {
select: Box::new(SelectInner {
distinctness: Distinctness::All,
columns: vec![ResultColumn::Expr(expr)],
from: None,
where_clause: Predicate::true_(),
order_by: None,
}),
compounds: Vec::new(),
},
limit: None,
}
}
pub fn single(
table: String,
result_columns: Vec<ResultColumn>,
where_clause: Predicate,
limit: Option<usize>,
distinct: Distinctness,
) -> Self {
Select {
body: SelectBody {
select: Box::new(SelectInner {
distinctness: distinct,
columns: result_columns,
from: Some(FromClause {
table,
joins: Vec::new(),
}),
where_clause,
order_by: None,
}),
compounds: Vec::new(),
},
limit,
}
}
pub fn compound(left: Select, right: Select, operator: CompoundOperator) -> Self {
let mut body = left.body;
body.compounds.push(CompoundSelect {
operator,
select: Box::new(right.body.select.as_ref().clone()),
});
Select {
body,
limit: left.limit.or(right.limit),
}
}
pub(crate) fn dependencies(&self) -> HashSet<String> {
if self.body.select.from.is_none() {
return HashSet::new();
}
let from = self.body.select.from.as_ref().unwrap();
let mut tables = HashSet::new();
tables.insert(from.table.clone());
tables.extend(from.dependencies());
for compound in &self.body.compounds {
tables.extend(
compound
.select
.from
.as_ref()
.map(|f| f.dependencies())
.unwrap_or(vec![])
.into_iter(),
);
}
tables
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SelectBody {
/// first select
pub select: Box<SelectInner>,
/// compounds
pub compounds: Vec<CompoundSelect>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct OrderBy {
pub columns: Vec<(String, SortOrder)>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SelectInner {
/// `DISTINCT`
pub distinctness: Distinctness,
/// columns
pub columns: Vec<ResultColumn>,
/// `FROM` clause
pub from: Option<FromClause>,
/// `WHERE` clause
pub where_clause: Predicate,
/// `ORDER BY` clause
pub order_by: Option<OrderBy>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum CompoundOperator {
/// `UNION`
Union,
/// `UNION ALL`
UnionAll,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct CompoundSelect {
/// operator
pub operator: CompoundOperator,
/// select
pub select: Box<SelectInner>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct FromClause {
/// table
pub table: String,
/// `JOIN`ed tables
pub joins: Vec<JoinedTable>,
}
impl FromClause {
fn to_sql_ast(&self) -> ast::FromClause {
ast::FromClause {
select: Some(Box::new(ast::SelectTable::Table(
ast::QualifiedName::single(ast::Name::from_str(&self.table)),
None,
None,
))),
joins: if self.joins.is_empty() {
None
} else {
Some(
self.joins
.iter()
.map(|join| ast::JoinedSelectTable {
operator: match join.join_type {
JoinType::Inner => {
ast::JoinOperator::TypedJoin(Some(ast::JoinType::INNER))
}
JoinType::Left => {
ast::JoinOperator::TypedJoin(Some(ast::JoinType::LEFT))
}
JoinType::Right => {
ast::JoinOperator::TypedJoin(Some(ast::JoinType::RIGHT))
}
JoinType::Full => {
ast::JoinOperator::TypedJoin(Some(ast::JoinType::OUTER))
}
JoinType::Cross => {
ast::JoinOperator::TypedJoin(Some(ast::JoinType::CROSS))
}
},
table: ast::SelectTable::Table(
ast::QualifiedName::single(ast::Name::from_str(&join.table)),
None,
None,
),
constraint: Some(ast::JoinConstraint::On(join.on.0.clone())),
})
.collect(),
)
},
}
}
pub(crate) fn dependencies(&self) -> Vec<String> {
let mut deps = vec![self.table.clone()];
for join in &self.joins {
deps.push(join.table.clone());
}
deps
}
}
impl Shadow for FromClause {
type Result = anyhow::Result<JoinTable>;
fn shadow(&self, env: &mut SimulatorTables) -> Self::Result {
let tables = &mut env.tables;
let first_table = tables
.iter()
.find(|t| t.name == self.table)
.context("Table not found")?;
let mut join_table = JoinTable {
tables: vec![first_table.clone()],
rows: Vec::new(),
};
for join in &self.joins {
let joined_table = tables
.iter()
.find(|t| t.name == join.table)
.context("Joined table not found")?;
join_table.tables.push(joined_table.clone());
match join.join_type {
JoinType::Inner => {
// Implement inner join logic
let join_rows = joined_table
.rows
.iter()
.filter(|row| join.on.test(row, joined_table))
.cloned()
.collect::<Vec<_>>();
// take a cartesian product of the rows
let all_row_pairs = join_table
.rows
.clone()
.into_iter()
.cartesian_product(join_rows.iter());
for (row1, row2) in all_row_pairs {
let row = row1.iter().chain(row2.iter()).cloned().collect::<Vec<_>>();
let is_in = join.on.test(&row, &join_table);
if is_in {
join_table.rows.push(row);
}
}
}
_ => todo!(),
}
}
Ok(join_table)
}
}
impl Shadow for SelectInner {
type Result = anyhow::Result<JoinTable>;
fn shadow(&self, env: &mut SimulatorTables) -> Self::Result {
if let Some(from) = &self.from {
let mut join_table = from.shadow(env)?;
let col_count = join_table.columns().count();
for row in &mut join_table.rows {
assert_eq!(
row.len(),
col_count,
"Row length does not match column length after join"
);
}
let join_clone = join_table.clone();
join_table
.rows
.retain(|row| self.where_clause.test(row, &join_clone));
if self.distinctness == Distinctness::Distinct {
join_table.rows.sort_unstable();
join_table.rows.dedup();
}
Ok(join_table)
} else {
assert!(self
.columns
.iter()
.all(|col| matches!(col, ResultColumn::Expr(_))));
// If `WHERE` is false, just return an empty table
if !self.where_clause.test(&[], &Table::anonymous(vec![])) {
return Ok(JoinTable {
tables: Vec::new(),
rows: Vec::new(),
});
}
// Compute the results of the column expressions and make a row
let mut row = Vec::new();
for col in &self.columns {
match col {
ResultColumn::Expr(expr) => {
let value = expr.eval(&[], &Table::anonymous(vec![]));
if let Some(value) = value {
row.push(value);
} else {
return Err(anyhow::anyhow!(
"Failed to evaluate expression in free select ({})",
expr.0.format_with_context(&EmptyContext {}).unwrap()
));
}
}
_ => unreachable!("Only expressions are allowed in free selects"),
}
}
Ok(JoinTable {
tables: Vec::new(),
rows: vec![row],
})
}
}
}
impl Shadow for Select {
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
fn shadow(&self, env: &mut SimulatorTables) -> Self::Result {
let first_result = self.body.select.shadow(env)?;
let mut rows = first_result.rows;
for compound in self.body.compounds.iter() {
let compound_results = compound.select.shadow(env)?;
match compound.operator {
CompoundOperator::Union => {
// Union means we need to combine the results, removing duplicates
let mut new_rows = compound_results.rows;
new_rows.extend(rows.clone());
new_rows.sort_unstable();
new_rows.dedup();
rows = new_rows;
}
CompoundOperator::UnionAll => {
// Union all means we just concatenate the results
rows.extend(compound_results.rows.into_iter());
}
}
}
Ok(rows)
}
}
impl Select {
pub fn to_sql_ast(&self) -> ast::Select {
ast::Select {
with: None,
body: ast::SelectBody {
select: Box::new(ast::OneSelect::Select(Box::new(ast::SelectInner {
distinctness: if self.body.select.distinctness == Distinctness::Distinct {
Some(ast::Distinctness::Distinct)
} else {
None
},
columns: self
.body
.select
.columns
.iter()
.map(|col| match col {
ResultColumn::Expr(expr) => {
ast::ResultColumn::Expr(expr.0.clone(), None)
}
ResultColumn::Star => ast::ResultColumn::Star,
ResultColumn::Column(name) => ast::ResultColumn::Expr(
ast::Expr::Id(ast::Name::Ident(name.clone())),
None,
),
})
.collect(),
from: self.body.select.from.as_ref().map(|f| f.to_sql_ast()),
where_clause: Some(self.body.select.where_clause.0.clone()),
group_by: None,
window_clause: None,
}))),
compounds: Some(
self.body
.compounds
.iter()
.map(|compound| ast::CompoundSelect {
operator: match compound.operator {
CompoundOperator::Union => ast::CompoundOperator::Union,
CompoundOperator::UnionAll => ast::CompoundOperator::UnionAll,
},
select: Box::new(ast::OneSelect::Select(Box::new(ast::SelectInner {
distinctness: Some(compound.select.distinctness),
columns: compound
.select
.columns
.iter()
.map(|col| match col {
ResultColumn::Expr(expr) => {
ast::ResultColumn::Expr(expr.0.clone(), None)
}
ResultColumn::Star => ast::ResultColumn::Star,
ResultColumn::Column(name) => ast::ResultColumn::Expr(
ast::Expr::Id(ast::Name::Ident(name.clone())),
None,
),
})
.collect(),
from: compound.select.from.as_ref().map(|f| f.to_sql_ast()),
where_clause: Some(compound.select.where_clause.0.clone()),
group_by: None,
window_clause: None,
}))),
})
.collect(),
),
},
order_by: self.body.select.order_by.as_ref().map(|o| {
o.columns
.iter()
.map(|(name, order)| ast::SortedColumn {
expr: ast::Expr::Id(ast::Name::Ident(name.clone())),
order: match order {
SortOrder::Asc => Some(ast::SortOrder::Asc),
SortOrder::Desc => Some(ast::SortOrder::Desc),
},
nulls: None,
})
.collect()
}),
limit: self.limit.map(|l| {
Box::new(ast::Limit {
expr: ast::Expr::Literal(ast::Literal::Numeric(l.to_string())),
offset: None,
})
}),
}
}
}
impl Display for Select {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.to_sql_ast().to_fmt_with_context(f, &EmptyContext {})
}
}
#[cfg(test)]
mod select_tests {
#[test]
fn test_select_display() {}
}

View File

@@ -0,0 +1,60 @@
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::{generation::Shadow, model::table::SimValue, runner::env::SimulatorTables};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct Begin {
pub(crate) immediate: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct Commit;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct Rollback;
impl Shadow for Begin {
type Result = Vec<Vec<SimValue>>;
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
tables.snapshot = Some(tables.tables.clone());
vec![]
}
}
impl Shadow for Commit {
type Result = Vec<Vec<SimValue>>;
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
tables.snapshot = None;
vec![]
}
}
impl Shadow for Rollback {
type Result = Vec<Vec<SimValue>>;
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
if let Some(tables_) = tables.snapshot.take() {
tables.tables = tables_;
}
vec![]
}
}
impl Display for Begin {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "BEGIN {}", if self.immediate { "IMMEDIATE" } else { "" })
}
}
impl Display for Commit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "COMMIT")
}
}
impl Display for Rollback {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ROLLBACK")
}
}

View File

@@ -0,0 +1,71 @@
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::{generation::Shadow, model::table::SimValue, runner::env::SimulatorTables};
use super::predicate::Predicate;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub(crate) struct Update {
pub(crate) table: String,
pub(crate) set_values: Vec<(String, SimValue)>, // Pair of value for set expressions => SET name=value
pub(crate) predicate: Predicate,
}
impl Update {
pub fn table(&self) -> &str {
&self.table
}
}
impl Shadow for Update {
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
let table = tables.tables.iter_mut().find(|t| t.name == self.table);
let table = if let Some(table) = table {
table
} else {
return Err(anyhow::anyhow!(
"Table {} does not exist. UPDATE statement ignored.",
self.table
));
};
let t2 = table.clone();
for row in table
.rows
.iter_mut()
.filter(|r| self.predicate.test(r, &t2))
{
for (column, set_value) in &self.set_values {
if let Some((idx, _)) = table
.columns
.iter()
.enumerate()
.find(|(_, c)| &c.name == column)
{
row[idx] = set_value.clone();
}
}
}
Ok(vec![])
}
}
impl Display for Update {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "UPDATE {} SET ", self.table)?;
for (i, (name, value)) in self.set_values.iter().enumerate() {
if i != 0 {
write!(f, ", ")?;
}
write!(f, "{name} = {value}")?;
}
write!(f, " WHERE {}", self.predicate)?;
Ok(())
}
}

View File

@@ -0,0 +1,428 @@
use std::{fmt::Display, hash::Hash, ops::Deref};
use serde::{Deserialize, Serialize};
use turso_core::{numeric::Numeric, types};
use turso_parser::ast;
use crate::model::query::predicate::Predicate;
pub(crate) struct Name(pub(crate) String);
impl Deref for Name {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Clone, Copy)]
pub struct ContextColumn<'a> {
pub table_name: &'a str,
pub column: &'a Column,
}
pub trait TableContext {
fn columns<'a>(&'a self) -> impl Iterator<Item = ContextColumn<'a>>;
fn rows(&self) -> &Vec<Vec<SimValue>>;
}
impl TableContext for Table {
fn columns<'a>(&'a self) -> impl Iterator<Item = ContextColumn<'a>> {
self.columns.iter().map(|col| ContextColumn {
column: col,
table_name: &self.name,
})
}
fn rows(&self) -> &Vec<Vec<SimValue>> {
&self.rows
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct Table {
pub(crate) name: String,
pub(crate) columns: Vec<Column>,
pub(crate) rows: Vec<Vec<SimValue>>,
pub(crate) indexes: Vec<String>,
}
impl Table {
pub fn anonymous(rows: Vec<Vec<SimValue>>) -> Self {
Self {
rows,
name: "".to_string(),
columns: vec![],
indexes: vec![],
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct Column {
pub(crate) name: String,
pub(crate) column_type: ColumnType,
pub(crate) primary: bool,
pub(crate) unique: bool,
}
// Uniquely defined by name in this case
impl Hash for Column {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name.hash(state);
}
}
impl PartialEq for Column {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl Eq for Column {}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) enum ColumnType {
Integer,
Float,
Text,
Blob,
}
impl Display for ColumnType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Integer => write!(f, "INTEGER"),
Self::Float => write!(f, "REAL"),
Self::Text => write!(f, "TEXT"),
Self::Blob => write!(f, "BLOB"),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct JoinedTable {
/// table name
pub table: String,
/// `JOIN` type
pub join_type: JoinType,
/// `ON` clause
pub on: Predicate,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum JoinType {
Inner,
Left,
Right,
Full,
Cross,
}
impl TableContext for JoinTable {
fn columns<'a>(&'a self) -> impl Iterator<Item = ContextColumn<'a>> {
self.tables.iter().flat_map(|table| table.columns())
}
fn rows(&self) -> &Vec<Vec<SimValue>> {
&self.rows
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct JoinTable {
pub tables: Vec<Table>,
pub rows: Vec<Vec<SimValue>>,
}
fn float_to_string<S>(float: &f64, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&format!("{float}"))
}
fn string_to_float<'de, D>(deserializer: D) -> Result<f64, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)]
pub(crate) struct SimValue(pub turso_core::Value);
fn to_sqlite_blob(bytes: &[u8]) -> String {
format!(
"X'{}'",
bytes
.iter()
.fold(String::new(), |acc, b| acc + &format!("{b:02X}"))
)
}
impl Display for SimValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.0 {
types::Value::Null => write!(f, "NULL"),
types::Value::Integer(i) => write!(f, "{i}"),
types::Value::Float(fl) => write!(f, "{fl}"),
value @ types::Value::Text(..) => write!(f, "'{value}'"),
types::Value::Blob(b) => write!(f, "{}", to_sqlite_blob(b)),
}
}
}
impl SimValue {
pub const FALSE: Self = SimValue(types::Value::Integer(0));
pub const TRUE: Self = SimValue(types::Value::Integer(1));
pub fn as_bool(&self) -> bool {
Numeric::from(&self.0).try_into_bool().unwrap_or_default()
}
// TODO: support more predicates
/// Returns a Result of a Binary Operation
///
/// TODO: forget collations for now
/// TODO: have the [ast::Operator::Equals], [ast::Operator::NotEquals], [ast::Operator::Greater],
/// [ast::Operator::GreaterEquals], [ast::Operator::Less], [ast::Operator::LessEquals] function to be extracted
/// into its functions in turso_core so that it can be used here
pub fn binary_compare(&self, other: &Self, operator: ast::Operator) -> SimValue {
match operator {
ast::Operator::Add => self.0.exec_add(&other.0).into(),
ast::Operator::And => self.0.exec_and(&other.0).into(),
ast::Operator::ArrowRight => todo!(),
ast::Operator::ArrowRightShift => todo!(),
ast::Operator::BitwiseAnd => self.0.exec_bit_and(&other.0).into(),
ast::Operator::BitwiseOr => self.0.exec_bit_or(&other.0).into(),
ast::Operator::BitwiseNot => todo!(), // TODO: Do not see any function usage of this operator in Core
ast::Operator::Concat => self.0.exec_concat(&other.0).into(),
ast::Operator::Equals => (self == other).into(),
ast::Operator::Divide => self.0.exec_divide(&other.0).into(),
ast::Operator::Greater => (self > other).into(),
ast::Operator::GreaterEquals => (self >= other).into(),
// TODO: Test these implementations
ast::Operator::Is => match (&self.0, &other.0) {
(types::Value::Null, types::Value::Null) => true.into(),
(types::Value::Null, _) => false.into(),
(_, types::Value::Null) => false.into(),
_ => self.binary_compare(other, ast::Operator::Equals),
},
ast::Operator::IsNot => self
.binary_compare(other, ast::Operator::Is)
.unary_exec(ast::UnaryOperator::Not),
ast::Operator::LeftShift => self.0.exec_shift_left(&other.0).into(),
ast::Operator::Less => (self < other).into(),
ast::Operator::LessEquals => (self <= other).into(),
ast::Operator::Modulus => self.0.exec_remainder(&other.0).into(),
ast::Operator::Multiply => self.0.exec_multiply(&other.0).into(),
ast::Operator::NotEquals => (self != other).into(),
ast::Operator::Or => self.0.exec_or(&other.0).into(),
ast::Operator::RightShift => self.0.exec_shift_right(&other.0).into(),
ast::Operator::Subtract => self.0.exec_subtract(&other.0).into(),
}
}
// TODO: support more operators. Copy the implementation for exec_glob
pub fn like_compare(&self, other: &Self, operator: ast::LikeOperator) -> bool {
match operator {
ast::LikeOperator::Glob => todo!(),
ast::LikeOperator::Like => {
// TODO: support ESCAPE `expr` option in AST
// TODO: regex cache
types::Value::exec_like(
None,
other.0.to_string().as_str(),
self.0.to_string().as_str(),
)
}
ast::LikeOperator::Match => todo!(),
ast::LikeOperator::Regexp => todo!(),
}
}
pub fn unary_exec(&self, operator: ast::UnaryOperator) -> SimValue {
let new_value = match operator {
ast::UnaryOperator::BitwiseNot => self.0.exec_bit_not(),
ast::UnaryOperator::Negative => {
SimValue(types::Value::Integer(0))
.binary_compare(self, ast::Operator::Subtract)
.0
}
ast::UnaryOperator::Not => self.0.exec_boolean_not(),
ast::UnaryOperator::Positive => self.0.clone(),
};
Self(new_value)
}
}
impl From<ast::Literal> for SimValue {
fn from(value: ast::Literal) -> Self {
Self::from(&value)
}
}
/// Converts a SQL string literal with already-escaped single quotes to a regular string by:
/// - Removing the enclosing single quotes
/// - Converting sequences of 2N single quotes ('''''') to N single quotes (''')
///
/// Assumes:
/// - The input starts and ends with a single quote
/// - The input contains a valid amount of single quotes inside the enclosing quotes;
/// i.e. any ' is escaped as a double ''
fn unescape_singlequotes(input: &str) -> String {
assert!(
input.starts_with('\'') && input.ends_with('\''),
"Input string must be wrapped in single quotes"
);
// Skip first and last characters (the enclosing quotes)
let inner = &input[1..input.len() - 1];
let mut result = String::with_capacity(inner.len());
let mut chars = inner.chars().peekable();
while let Some(c) = chars.next() {
if c == '\'' {
// Count consecutive single quotes
let mut quote_count = 1;
while chars.peek() == Some(&'\'') {
quote_count += 1;
chars.next();
}
assert!(
quote_count % 2 == 0,
"Expected even number of quotes, got {quote_count} in string {input}"
);
// For every pair of quotes, output one quote
for _ in 0..(quote_count / 2) {
result.push('\'');
}
} else {
result.push(c);
}
}
result
}
/// Escapes a string by doubling contained single quotes and then wrapping it in single quotes.
fn escape_singlequotes(input: &str) -> String {
let mut result = String::with_capacity(input.len() + 2);
result.push('\'');
result.push_str(&input.replace("'", "''"));
result.push('\'');
result
}
impl From<&ast::Literal> for SimValue {
fn from(value: &ast::Literal) -> Self {
let new_value = match value {
ast::Literal::Null => types::Value::Null,
ast::Literal::Numeric(number) => Numeric::from(number).into(),
ast::Literal::String(string) => types::Value::build_text(unescape_singlequotes(string)),
ast::Literal::Blob(blob) => types::Value::Blob(
blob.as_bytes()
.chunks_exact(2)
.map(|pair| {
// We assume that sqlite3-parser has already validated that
// the input is valid hex string, thus unwrap is safe.
let hex_byte = std::str::from_utf8(pair).unwrap();
u8::from_str_radix(hex_byte, 16).unwrap()
})
.collect(),
),
ast::Literal::Keyword(keyword) => match keyword.to_uppercase().as_str() {
"TRUE" => types::Value::Integer(1),
"FALSE" => types::Value::Integer(0),
"NULL" => types::Value::Null,
_ => unimplemented!("Unsupported keyword literal: {}", keyword),
},
lit => unimplemented!("{:?}", lit),
};
Self(new_value)
}
}
impl From<SimValue> for ast::Literal {
fn from(value: SimValue) -> Self {
Self::from(&value)
}
}
impl From<&SimValue> for ast::Literal {
fn from(value: &SimValue) -> Self {
match &value.0 {
types::Value::Null => Self::Null,
types::Value::Integer(i) => Self::Numeric(i.to_string()),
types::Value::Float(f) => Self::Numeric(f.to_string()),
text @ types::Value::Text(..) => Self::String(escape_singlequotes(&text.to_string())),
types::Value::Blob(blob) => Self::Blob(hex::encode(blob)),
}
}
}
impl From<bool> for SimValue {
fn from(value: bool) -> Self {
if value {
SimValue::TRUE
} else {
SimValue::FALSE
}
}
}
impl From<SimValue> for turso_core::types::Value {
fn from(value: SimValue) -> Self {
value.0
}
}
impl From<&SimValue> for turso_core::types::Value {
fn from(value: &SimValue) -> Self {
value.0.clone()
}
}
impl From<turso_core::types::Value> for SimValue {
fn from(value: turso_core::types::Value) -> Self {
Self(value)
}
}
impl From<&turso_core::types::Value> for SimValue {
fn from(value: &turso_core::types::Value) -> Self {
Self(value.clone())
}
}
#[cfg(test)]
mod tests {
use crate::model::table::{escape_singlequotes, unescape_singlequotes};
#[test]
fn test_unescape_singlequotes() {
assert_eq!(unescape_singlequotes("'hello'"), "hello");
assert_eq!(unescape_singlequotes("'O''Reilly'"), "O'Reilly");
assert_eq!(
unescape_singlequotes("'multiple''single''quotes'"),
"multiple'single'quotes"
);
assert_eq!(unescape_singlequotes("'test''''test'"), "test''test");
assert_eq!(unescape_singlequotes("'many''''''quotes'"), "many'''quotes");
}
#[test]
fn test_escape_singlequotes() {
assert_eq!(escape_singlequotes("hello"), "'hello'");
assert_eq!(escape_singlequotes("O'Reilly"), "'O''Reilly'");
assert_eq!(
escape_singlequotes("multiple'single'quotes"),
"'multiple''single''quotes'"
);
assert_eq!(escape_singlequotes("test''test"), "'test''''test'");
assert_eq!(escape_singlequotes("many'''quotes"), "'many''''''quotes'");
}
}