mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-23 08:55:40 +01:00
copy generation code from simulator
This commit is contained in:
45
sql_generation/model/query/create.rs
Normal file
45
sql_generation/model/query/create.rs
Normal 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, ")")
|
||||
}
|
||||
}
|
||||
106
sql_generation/model/query/create_index.rs
Normal file
106
sql_generation/model/query/create_index.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
41
sql_generation/model/query/delete.rs
Normal file
41
sql_generation/model/query/delete.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
34
sql_generation/model/query/drop.rs
Normal file
34
sql_generation/model/query/drop.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
87
sql_generation/model/query/insert.rs
Normal file
87
sql_generation/model/query/insert.rs
Normal 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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
129
sql_generation/model/query/mod.rs
Normal file
129
sql_generation/model/query/mod.rs
Normal 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!()
|
||||
}
|
||||
}
|
||||
146
sql_generation/model/query/predicate.rs
Normal file
146
sql_generation/model/query/predicate.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
496
sql_generation/model/query/select.rs
Normal file
496
sql_generation/model/query/select.rs
Normal 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() {}
|
||||
}
|
||||
60
sql_generation/model/query/transaction.rs
Normal file
60
sql_generation/model/query/transaction.rs
Normal 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")
|
||||
}
|
||||
}
|
||||
71
sql_generation/model/query/update.rs
Normal file
71
sql_generation/model/query/update.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user