mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-29 14:04:22 +01:00
Merge 'Fix two issues in simulator' from Jussi Saurio
## 1. select equal number of columns per compound subselect in simulator #2609 fixed compound selects incorrectly, introducing another bug: now the sim can SELECT a different number of columns per subselect, which is illegal. this PR forces the sim to select the same number of cols per subselect. Depends on #2614 , otherwise the sim constantly fails whenever it decides to do a compound select with DISTINCT. Closes #2611 ## 2. introduce TableContext for the simulator to properly generate predicates for Joins The simulator has a construct called `JoinTable` which is really a `JoinContext` that includes all the columns from the so-far joined tables, so that the next table to be joined can refer to columns from any of those tables. @pedrocarlo 's commit fixes an issue where `JoinTable` would incorrectly generate predicates with empty table names. Related: #2618 Closes #2616
This commit is contained in:
@@ -11,7 +11,7 @@ use crate::{
|
||||
},
|
||||
model::{
|
||||
query::predicate::Predicate,
|
||||
table::{SimValue, Table},
|
||||
table::{SimValue, Table, TableContext},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -241,39 +241,30 @@ impl Predicate {
|
||||
}
|
||||
|
||||
impl SimplePredicate {
|
||||
/// Generates a true [ast::Expr::Binary] [SimplePredicate] from a [Table] for a row in the table
|
||||
pub fn true_binary<R: rand::Rng>(rng: &mut R, table: &Table, row: &[SimValue]) -> Self {
|
||||
/// Generates a true [ast::Expr::Binary] [SimplePredicate] from a [TableContext] for a row in the table
|
||||
pub fn true_binary<R: rand::Rng, T: TableContext>(
|
||||
rng: &mut R,
|
||||
table: &T,
|
||||
row: &[SimValue],
|
||||
) -> Self {
|
||||
// Pick a random column
|
||||
let column_index = rng.gen_range(0..table.columns.len());
|
||||
let mut column = table.columns[column_index].clone();
|
||||
let columns = table.columns().collect::<Vec<_>>();
|
||||
let column_index = rng.gen_range(0..columns.len());
|
||||
let column = columns[column_index];
|
||||
let column_value = &row[column_index];
|
||||
let mut table_name = table.name.clone();
|
||||
let table_name = column.table_name;
|
||||
// Avoid creation of NULLs
|
||||
if row.is_empty() {
|
||||
return SimplePredicate(Predicate(Expr::Literal(SimValue::TRUE.into())));
|
||||
}
|
||||
|
||||
if table.name.is_empty() {
|
||||
// If the table name is empty, we cannot create a qualified expression
|
||||
// so we use the column name directly
|
||||
let mut splitted = column.name.split('.');
|
||||
table_name = splitted
|
||||
.next()
|
||||
.expect("Column name should have a table prefix for a joined table")
|
||||
.to_string();
|
||||
column.name = splitted
|
||||
.next()
|
||||
.expect("Column name should have a column suffix for a joined table")
|
||||
.to_string();
|
||||
}
|
||||
|
||||
let expr = one_of(
|
||||
vec![
|
||||
Box::new(|_rng| {
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::from_str(&table_name),
|
||||
ast::Name::from_str(&column.name),
|
||||
ast::Name::from_str(table_name),
|
||||
ast::Name::from_str(&column.column.name),
|
||||
)),
|
||||
ast::Operator::Equals,
|
||||
Box::new(Expr::Literal(column_value.into())),
|
||||
@@ -283,8 +274,8 @@ impl SimplePredicate {
|
||||
let lt_value = LTValue::arbitrary_from(rng, column_value).0;
|
||||
Expr::Binary(
|
||||
Box::new(Expr::Qualified(
|
||||
ast::Name::from_str(&table_name),
|
||||
ast::Name::from_str(&column.name),
|
||||
ast::Name::from_str(table_name),
|
||||
ast::Name::from_str(&column.column.name),
|
||||
)),
|
||||
ast::Operator::Greater,
|
||||
Box::new(Expr::Literal(lt_value.into())),
|
||||
@@ -294,8 +285,8 @@ impl SimplePredicate {
|
||||
let gt_value = GTValue::arbitrary_from(rng, column_value).0;
|
||||
Expr::Binary(
|
||||
Box::new(Expr::Qualified(
|
||||
ast::Name::from_str(&table_name),
|
||||
ast::Name::from_str(&column.name),
|
||||
ast::Name::from_str(table_name),
|
||||
ast::Name::from_str(&column.column.name),
|
||||
)),
|
||||
ast::Operator::Less,
|
||||
Box::new(Expr::Literal(gt_value.into())),
|
||||
@@ -307,42 +298,30 @@ impl SimplePredicate {
|
||||
SimplePredicate(Predicate(expr))
|
||||
}
|
||||
|
||||
/// Generates a false [ast::Expr::Binary] [SimplePredicate] from a [Table] for a row in the table
|
||||
pub fn false_binary<R: rand::Rng>(rng: &mut R, table: &Table, row: &[SimValue]) -> Self {
|
||||
/// Generates a false [ast::Expr::Binary] [SimplePredicate] from a [TableContext] for a row in the table
|
||||
pub fn false_binary<R: rand::Rng, T: TableContext>(
|
||||
rng: &mut R,
|
||||
table: &T,
|
||||
row: &[SimValue],
|
||||
) -> Self {
|
||||
let columns = table.columns().collect::<Vec<_>>();
|
||||
// Pick a random column
|
||||
let column_index = rng.gen_range(0..table.columns.len());
|
||||
// println!("column_index: {}", column_index);
|
||||
// println!("table.columns: {:?}", table.columns);
|
||||
// println!("row: {:?}", row);
|
||||
let mut column = table.columns[column_index].clone();
|
||||
let column_index = rng.gen_range(0..columns.len());
|
||||
let column = columns[column_index];
|
||||
let column_value = &row[column_index];
|
||||
let mut table_name = table.name.clone();
|
||||
let table_name = column.table_name;
|
||||
// Avoid creation of NULLs
|
||||
if row.is_empty() {
|
||||
return SimplePredicate(Predicate(Expr::Literal(SimValue::FALSE.into())));
|
||||
}
|
||||
|
||||
if table.name.is_empty() {
|
||||
// If the table name is empty, we cannot create a qualified expression
|
||||
// so we use the column name directly
|
||||
let mut splitted = column.name.split('.');
|
||||
table_name = splitted
|
||||
.next()
|
||||
.expect("Column name should have a table prefix for a joined table")
|
||||
.to_string();
|
||||
column.name = splitted
|
||||
.next()
|
||||
.expect("Column name should have a column suffix for a joined table")
|
||||
.to_string();
|
||||
}
|
||||
|
||||
let expr = one_of(
|
||||
vec![
|
||||
Box::new(|_rng| {
|
||||
Expr::Binary(
|
||||
Box::new(Expr::Qualified(
|
||||
ast::Name::from_str(&table_name),
|
||||
ast::Name::from_str(&column.name),
|
||||
ast::Name::from_str(table_name),
|
||||
ast::Name::from_str(&column.column.name),
|
||||
)),
|
||||
ast::Operator::NotEquals,
|
||||
Box::new(Expr::Literal(column_value.into())),
|
||||
@@ -352,8 +331,8 @@ impl SimplePredicate {
|
||||
let gt_value = GTValue::arbitrary_from(rng, column_value).0;
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::from_str(&table_name),
|
||||
ast::Name::from_str(&column.name),
|
||||
ast::Name::from_str(table_name),
|
||||
ast::Name::from_str(&column.column.name),
|
||||
)),
|
||||
ast::Operator::Greater,
|
||||
Box::new(Expr::Literal(gt_value.into())),
|
||||
@@ -363,8 +342,8 @@ impl SimplePredicate {
|
||||
let lt_value = LTValue::arbitrary_from(rng, column_value).0;
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::from_str(&table_name),
|
||||
ast::Name::from_str(&column.name),
|
||||
ast::Name::from_str(table_name),
|
||||
ast::Name::from_str(&column.column.name),
|
||||
)),
|
||||
ast::Operator::Less,
|
||||
Box::new(Expr::Literal(lt_value.into())),
|
||||
@@ -381,27 +360,21 @@ impl CompoundPredicate {
|
||||
/// Decide if you want to create an AND or an OR
|
||||
///
|
||||
/// Creates a Compound Predicate that is TRUE or FALSE for at least a single row
|
||||
pub fn from_table_binary<R: rand::Rng>(
|
||||
pub fn from_table_binary<R: rand::Rng, T: TableContext>(
|
||||
rng: &mut R,
|
||||
table: &Table,
|
||||
table: &T,
|
||||
predicate_value: bool,
|
||||
) -> Self {
|
||||
// Cannot pick a row if the table is empty
|
||||
if table.rows.is_empty() {
|
||||
let rows = table.rows();
|
||||
if rows.is_empty() {
|
||||
return Self(if predicate_value {
|
||||
Predicate::true_()
|
||||
} else {
|
||||
Predicate::false_()
|
||||
});
|
||||
}
|
||||
let row = pick(&table.rows, rng);
|
||||
|
||||
tracing::trace!(
|
||||
"Creating a {} CompoundPredicate for table: {} and row: {:?}",
|
||||
if predicate_value { "true" } else { "false" },
|
||||
table.name,
|
||||
row
|
||||
);
|
||||
let row = pick(rows, rng);
|
||||
|
||||
let predicate = if rng.gen_bool(0.7) {
|
||||
// An AND for true requires each of its children to be true
|
||||
|
||||
@@ -3,7 +3,7 @@ use turso_sqlite3_parser::ast::{self, Expr};
|
||||
|
||||
use crate::model::{
|
||||
query::predicate::Predicate,
|
||||
table::{SimValue, Table},
|
||||
table::{SimValue, Table, TableContext},
|
||||
};
|
||||
|
||||
use super::{one_of, ArbitraryFrom};
|
||||
@@ -17,11 +17,8 @@ struct CompoundPredicate(Predicate);
|
||||
#[derive(Debug)]
|
||||
struct SimplePredicate(Predicate);
|
||||
|
||||
impl<A: AsRef<[SimValue]>> ArbitraryFrom<(&Table, A, bool)> for SimplePredicate {
|
||||
fn arbitrary_from<R: Rng>(
|
||||
rng: &mut R,
|
||||
(table, row, predicate_value): (&Table, A, bool),
|
||||
) -> Self {
|
||||
impl<A: AsRef<[SimValue]>, T: TableContext> ArbitraryFrom<(&T, A, bool)> for SimplePredicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, (table, row, predicate_value): (&T, A, bool)) -> Self {
|
||||
let row = row.as_ref();
|
||||
// Pick an operator
|
||||
let choice = rng.gen_range(0..2);
|
||||
@@ -41,21 +38,21 @@ impl<A: AsRef<[SimValue]>> ArbitraryFrom<(&Table, A, bool)> for SimplePredicate
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<(&Table, bool)> for CompoundPredicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, (table, predicate_value): (&Table, bool)) -> Self {
|
||||
impl<T: TableContext> ArbitraryFrom<(&T, bool)> for CompoundPredicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, (table, predicate_value): (&T, bool)) -> Self {
|
||||
CompoundPredicate::from_table_binary(rng, table, predicate_value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<&Table> for Predicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, table: &Table) -> Self {
|
||||
impl<T: TableContext> ArbitraryFrom<&T> for Predicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, table: &T) -> Self {
|
||||
let predicate_value = rng.gen_bool(0.5);
|
||||
Predicate::arbitrary_from(rng, (table, predicate_value)).parens()
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<(&Table, bool)> for Predicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, (table, predicate_value): (&Table, bool)) -> Self {
|
||||
impl<T: TableContext> ArbitraryFrom<(&T, bool)> for Predicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, (table, predicate_value): (&T, bool)) -> Self {
|
||||
CompoundPredicate::arbitrary_from(rng, (table, predicate_value)).0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
generation::{backtrack, pick, predicate::SimplePredicate, ArbitraryFromMaybe},
|
||||
model::{
|
||||
query::predicate::Predicate,
|
||||
table::{SimValue, Table},
|
||||
table::{SimValue, TableContext},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -99,10 +99,15 @@ impl ArbitraryFromMaybe<(&Vec<&SimValue>, bool)> for BitNotValue {
|
||||
|
||||
// TODO: have some more complex generation with columns names here as well
|
||||
impl SimplePredicate {
|
||||
/// Generates a true [ast::Expr::Unary] [SimplePredicate] from a [Table] for some values in the table
|
||||
pub fn true_unary<R: rand::Rng>(rng: &mut R, table: &Table, row: &[SimValue]) -> Self {
|
||||
/// Generates a true [ast::Expr::Unary] [SimplePredicate] from a [TableContext] for some values in the table
|
||||
pub fn true_unary<R: rand::Rng, T: TableContext>(
|
||||
rng: &mut R,
|
||||
table: &T,
|
||||
row: &[SimValue],
|
||||
) -> Self {
|
||||
let columns = table.columns().collect::<Vec<_>>();
|
||||
// Pick a random column
|
||||
let column_index = rng.gen_range(0..table.columns.len());
|
||||
let column_index = rng.gen_range(0..columns.len());
|
||||
let column_value = &row[column_index];
|
||||
let num_retries = row.len();
|
||||
// Avoid creation of NULLs
|
||||
@@ -160,10 +165,15 @@ impl SimplePredicate {
|
||||
))
|
||||
}
|
||||
|
||||
/// Generates a false [ast::Expr::Unary] [SimplePredicate] from a [Table] for a row in the table
|
||||
pub fn false_unary<R: rand::Rng>(rng: &mut R, table: &Table, row: &[SimValue]) -> Self {
|
||||
/// Generates a false [ast::Expr::Unary] [SimplePredicate] from a [TableContext] for a row in the table
|
||||
pub fn false_unary<R: rand::Rng, T: TableContext>(
|
||||
rng: &mut R,
|
||||
table: &T,
|
||||
row: &[SimValue],
|
||||
) -> Self {
|
||||
let columns = table.columns().collect::<Vec<_>>();
|
||||
// Pick a random column
|
||||
let column_index = rng.gen_range(0..table.columns.len());
|
||||
let column_index = rng.gen_range(0..columns.len());
|
||||
let column_value = &row[column_index];
|
||||
let num_retries = row.len();
|
||||
// Avoid creation of NULLs
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use crate::generation::{Arbitrary, ArbitraryFrom, ArbitrarySizedFrom, Shadow};
|
||||
use crate::model::query::predicate::Predicate;
|
||||
use crate::model::query::select::{
|
||||
CompoundOperator, CompoundSelect, Distinctness, FromClause, JoinTable, JoinType, JoinedTable,
|
||||
OrderBy, ResultColumn, SelectBody, SelectInner,
|
||||
CompoundOperator, CompoundSelect, Distinctness, FromClause, OrderBy, ResultColumn, SelectBody,
|
||||
SelectInner,
|
||||
};
|
||||
use crate::model::query::update::Update;
|
||||
use crate::model::query::{Create, Delete, Drop, Insert, Query, Select};
|
||||
use crate::model::table::{SimValue, Table};
|
||||
use crate::model::table::{JoinTable, JoinType, JoinedTable, SimValue, Table, TableContext};
|
||||
use crate::SimulatorEnv;
|
||||
use itertools::Itertools;
|
||||
use rand::Rng;
|
||||
@@ -39,27 +39,32 @@ impl ArbitraryFrom<&Vec<Table>> for FromClause {
|
||||
|
||||
let name = table.name.clone();
|
||||
|
||||
let mut table_context = JoinTable {
|
||||
tables: Vec::new(),
|
||||
rows: Vec::new(),
|
||||
};
|
||||
|
||||
let joins: Vec<_> = (0..num_joins)
|
||||
.filter_map(|_| {
|
||||
if tables.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let join_table = pick(&tables, rng).clone();
|
||||
let joined_table_name = join_table.name.clone();
|
||||
|
||||
tables.retain(|t| t.name != join_table.name);
|
||||
table = JoinTable {
|
||||
tables: vec![table.clone(), join_table.clone()],
|
||||
rows: table
|
||||
.rows
|
||||
.iter()
|
||||
.cartesian_product(join_table.rows.iter())
|
||||
.map(|(t_row, j_row)| {
|
||||
let mut row = t_row.clone();
|
||||
row.extend(j_row.clone());
|
||||
row
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
.into_table();
|
||||
table_context.rows = table_context
|
||||
.rows
|
||||
.iter()
|
||||
.cartesian_product(join_table.rows.iter())
|
||||
.map(|(t_row, j_row)| {
|
||||
let mut row = t_row.clone();
|
||||
row.extend(j_row.clone());
|
||||
row
|
||||
})
|
||||
.collect();
|
||||
// TODO: inneficient. use a Deque to push_front?
|
||||
table_context.tables.insert(0, join_table);
|
||||
for row in &mut table.rows {
|
||||
assert_eq!(
|
||||
row.len(),
|
||||
@@ -70,7 +75,7 @@ impl ArbitraryFrom<&Vec<Table>> for FromClause {
|
||||
|
||||
let predicate = Predicate::arbitrary_from(rng, &table);
|
||||
Some(JoinedTable {
|
||||
table: join_table.name.clone(),
|
||||
table: joined_table_name,
|
||||
join_type: JoinType::Inner,
|
||||
on: predicate,
|
||||
})
|
||||
@@ -87,8 +92,8 @@ impl ArbitraryFrom<&SimulatorEnv> for SelectInner {
|
||||
// todo: this is a temporary hack because env is not separated from the tables
|
||||
let join_table = from
|
||||
.shadow(&mut tables)
|
||||
.expect("Failed to shadow FromClause")
|
||||
.into_table();
|
||||
.expect("Failed to shadow FromClause");
|
||||
let cuml_col_count = join_table.columns().count();
|
||||
|
||||
let order_by = 'order_by: {
|
||||
if rng.gen_bool(0.3) {
|
||||
@@ -98,7 +103,6 @@ impl ArbitraryFrom<&SimulatorEnv> for SelectInner {
|
||||
.map(|j| j.table.clone())
|
||||
.chain(std::iter::once(from.table.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
let cuml_col_count = join_table.columns.len();
|
||||
let order_by_col_count =
|
||||
(rng.gen::<f64>() * rng.gen::<f64>() * (cuml_col_count as f64)) as usize; // skew towards 0
|
||||
if order_by_col_count == 0 {
|
||||
@@ -144,6 +148,43 @@ impl ArbitraryFrom<&SimulatorEnv> for SelectInner {
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitrarySizedFrom<&SimulatorEnv> for SelectInner {
|
||||
fn arbitrary_sized_from<R: Rng>(
|
||||
rng: &mut R,
|
||||
env: &SimulatorEnv,
|
||||
num_result_columns: usize,
|
||||
) -> Self {
|
||||
let mut select_inner = SelectInner::arbitrary_from(rng, env);
|
||||
let select_from = &select_inner.from.as_ref().unwrap();
|
||||
let table_names = select_from
|
||||
.joins
|
||||
.iter()
|
||||
.map(|j| j.table.clone())
|
||||
.chain(std::iter::once(select_from.table.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let flat_columns_names = table_names
|
||||
.iter()
|
||||
.flat_map(|t| {
|
||||
env.tables
|
||||
.iter()
|
||||
.find(|table| table.name == *t)
|
||||
.unwrap()
|
||||
.columns
|
||||
.iter()
|
||||
.map(|c| format!("{}.{}", t.clone(), c.name))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let selected_columns = pick_unique(&flat_columns_names, num_result_columns, rng);
|
||||
let mut columns = Vec::new();
|
||||
for column_name in selected_columns {
|
||||
columns.push(ResultColumn::Column(column_name.clone()));
|
||||
}
|
||||
select_inner.columns = columns;
|
||||
select_inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Arbitrary for Distinctness {
|
||||
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
|
||||
match rng.gen_range(0..=5) {
|
||||
@@ -191,10 +232,15 @@ impl ArbitraryFrom<&SimulatorEnv> for Select {
|
||||
0
|
||||
};
|
||||
|
||||
let first = SelectInner::arbitrary_from(rng, env);
|
||||
let min_column_count_across_tables =
|
||||
env.tables.iter().map(|t| t.columns.len()).min().unwrap();
|
||||
|
||||
let num_result_columns = rng.gen_range(1..=min_column_count_across_tables);
|
||||
|
||||
let first = SelectInner::arbitrary_sized_from(rng, env, num_result_columns);
|
||||
|
||||
let rest: Vec<SelectInner> = (0..num_compound_selects)
|
||||
.map(|_| SelectInner::arbitrary_from(rng, env))
|
||||
.map(|_| SelectInner::arbitrary_sized_from(rng, env, num_result_columns))
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::fmt::Display;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use turso_sqlite3_parser::ast::{self, fmt::ToTokens};
|
||||
|
||||
use crate::model::table::{SimValue, Table};
|
||||
use crate::model::table::{SimValue, Table, TableContext};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Predicate(pub ast::Expr);
|
||||
@@ -78,7 +78,7 @@ impl Predicate {
|
||||
expr_to_value(&self.0, row, table)
|
||||
}
|
||||
|
||||
pub(crate) fn test(&self, row: &[SimValue], table: &Table) -> bool {
|
||||
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())
|
||||
}
|
||||
@@ -88,19 +88,23 @@ impl Predicate {
|
||||
// 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(expr: &ast::Expr, row: &[SimValue], table: &Table) -> Option<SimValue> {
|
||||
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)) => {
|
||||
assert_eq!(row.len(), table.columns.len());
|
||||
table
|
||||
.columns
|
||||
let columns = table.columns().collect::<Vec<_>>();
|
||||
assert_eq!(row.len(), columns.len());
|
||||
columns
|
||||
.iter()
|
||||
.zip(row.iter())
|
||||
.find(|(column, _)| column.name == *col_name)
|
||||
.find(|(column, _)| column.column.name == *col_name)
|
||||
.map(|(_, value)| value)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
generation::Shadow,
|
||||
model::{
|
||||
query::EmptyContext,
|
||||
table::{SimValue, Table},
|
||||
table::{JoinTable, JoinType, JoinedTable, SimValue, Table, TableContext},
|
||||
},
|
||||
runner::env::SimulatorTables,
|
||||
};
|
||||
@@ -239,51 +239,6 @@ impl FromClause {
|
||||
deps
|
||||
}
|
||||
}
|
||||
#[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,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct JoinTable {
|
||||
pub tables: Vec<Table>,
|
||||
pub rows: Vec<Vec<SimValue>>,
|
||||
}
|
||||
|
||||
impl JoinTable {
|
||||
pub(crate) fn into_table(self) -> Table {
|
||||
let t = Table {
|
||||
name: "".to_string(),
|
||||
columns: self
|
||||
.tables
|
||||
.iter()
|
||||
.flat_map(|t| {
|
||||
t.columns.iter().map(|c| {
|
||||
let mut c = c.clone();
|
||||
c.name = format!("{}.{}", t.name, c.name);
|
||||
c
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
rows: self.rows,
|
||||
};
|
||||
t
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for FromClause {
|
||||
type Result = anyhow::Result<JoinTable>;
|
||||
@@ -327,8 +282,7 @@ impl Shadow for FromClause {
|
||||
for (row1, row2) in all_row_pairs {
|
||||
let row = row1.iter().chain(row2.iter()).cloned().collect::<Vec<_>>();
|
||||
|
||||
let as_table = join_table.clone().into_table();
|
||||
let is_in = join.on.test(&row, &as_table);
|
||||
let is_in = join.on.test(&row, &join_table);
|
||||
|
||||
if is_in {
|
||||
join_table.rows.push(row);
|
||||
@@ -348,18 +302,19 @@ impl Shadow for SelectInner {
|
||||
fn shadow(&self, env: &mut SimulatorTables) -> Self::Result {
|
||||
if let Some(from) = &self.from {
|
||||
let mut join_table = from.shadow(env)?;
|
||||
let as_table = join_table.clone().into_table();
|
||||
let col_count = join_table.columns().count();
|
||||
for row in &mut join_table.rows {
|
||||
assert_eq!(
|
||||
row.len(),
|
||||
as_table.columns.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, &as_table));
|
||||
.retain(|row| self.where_clause.test(row, &join_clone));
|
||||
|
||||
if self.distinctness == Distinctness::Distinct {
|
||||
join_table.rows.sort_unstable();
|
||||
@@ -414,7 +369,7 @@ impl Shadow for Select {
|
||||
fn shadow(&self, env: &mut SimulatorTables) -> Self::Result {
|
||||
let first_result = self.body.select.shadow(env)?;
|
||||
|
||||
let mut rows = first_result.into_table().rows;
|
||||
let mut rows = first_result.rows;
|
||||
|
||||
for compound in self.body.compounds.iter() {
|
||||
let compound_results = compound.select.shadow(env)?;
|
||||
@@ -422,7 +377,7 @@ impl Shadow for Select {
|
||||
match compound.operator {
|
||||
CompoundOperator::Union => {
|
||||
// Union means we need to combine the results, removing duplicates
|
||||
let mut new_rows = compound_results.into_table().rows;
|
||||
let mut new_rows = compound_results.rows;
|
||||
new_rows.extend(rows.clone());
|
||||
new_rows.sort_unstable();
|
||||
new_rows.dedup();
|
||||
|
||||
@@ -4,6 +4,8 @@ use serde::{Deserialize, Serialize};
|
||||
use turso_core::{numeric::Numeric, types};
|
||||
use turso_sqlite3_parser::ast;
|
||||
|
||||
use crate::model::query::predicate::Predicate;
|
||||
|
||||
pub(crate) struct Name(pub(crate) String);
|
||||
|
||||
impl Deref for Name {
|
||||
@@ -14,11 +16,35 @@ impl Deref for Name {
|
||||
}
|
||||
}
|
||||
|
||||
#[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) rows: Vec<Vec<SimValue>>,
|
||||
pub(crate) name: String,
|
||||
pub(crate) columns: Vec<Column>,
|
||||
pub(crate) rows: Vec<Vec<SimValue>>,
|
||||
}
|
||||
|
||||
impl Table {
|
||||
@@ -73,6 +99,41 @@ impl Display for ColumnType {
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
|
||||
Reference in New Issue
Block a user