Merge 'add interactive transaction to property insert-values-select' from Pere Diaz Bou
Some checks are pending
Build and push limbo-sim image / deploy (push) Waiting to run
Go Tests / test (push) Waiting to run
Java Tests / test (push) Waiting to run
JavaScript / stable - aarch64-apple-darwin - node@20 (push) Waiting to run
JavaScript / stable - x86_64-apple-darwin - node@20 (push) Waiting to run
JavaScript / stable - x86_64-pc-windows-msvc - node@20 (push) Waiting to run
JavaScript / stable - x86_64-unknown-linux-gnu - node@20 (push) Waiting to run
JavaScript / Test bindings on x86_64-apple-darwin - node@20 (push) Blocked by required conditions
JavaScript / Test bindings on Linux-x64-gnu - node@20 (push) Blocked by required conditions
JavaScript / Build universal macOS binary (push) Blocked by required conditions
JavaScript / Publish (push) Blocked by required conditions
Python / configure-strategy (push) Waiting to run
Python / test (push) Blocked by required conditions
Python / lint (push) Waiting to run
Python / linux (x86_64) (push) Waiting to run
Python / macos-x86_64 (x86_64) (push) Waiting to run
Python / macos-arm64 (aarch64) (push) Waiting to run
Python / sdist (push) Waiting to run
Python / Release (push) Blocked by required conditions
Rust / cargo-fmt-check (push) Waiting to run
Rust / build-native (blacksmith-4vcpu-ubuntu-2404) (push) Waiting to run
Rust / build-wasm (push) Waiting to run
Rust / simulator (push) Waiting to run
Rust / test-limbo (push) Waiting to run
Rust / build-native (macos-latest) (push) Waiting to run
Rust / build-native (windows-latest) (push) Waiting to run
Rust / clippy (push) Waiting to run
Rust / test-sqlite (push) Waiting to run
Rust Benchmarks+Nyrkiö / bench (push) Waiting to run
Rust Benchmarks+Nyrkiö / clickbench (push) Waiting to run
Rust Benchmarks+Nyrkiö / tpc-h-criterion (push) Waiting to run
Rust Benchmarks+Nyrkiö / tpc-h (push) Waiting to run
Rust Benchmarks+Nyrkiö / vfs-bench-compile (push) Waiting to run

Closes #1958
This commit is contained in:
Pekka Enberg 2025-07-07 14:29:02 +03:00
commit 931a33642e
5 changed files with 162 additions and 18 deletions

View file

@ -217,20 +217,26 @@ pub(crate) struct InteractionStats {
pub(crate) create_count: usize,
pub(crate) create_index_count: usize,
pub(crate) drop_count: usize,
pub(crate) begin_count: usize,
pub(crate) commit_count: usize,
pub(crate) rollback_count: usize,
}
impl Display for InteractionStats {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Read: {}, Write: {}, Delete: {}, Update: {}, Create: {}, CreateIndex: {}, Drop: {}",
"Read: {}, Write: {}, Delete: {}, Update: {}, Create: {}, CreateIndex: {}, Drop: {}, Begin: {}, Commit: {}, Rollback: {}",
self.read_count,
self.write_count,
self.delete_count,
self.update_count,
self.create_count,
self.create_index_count,
self.drop_count
self.drop_count,
self.begin_count,
self.commit_count,
self.rollback_count,
)
}
}
@ -307,6 +313,9 @@ impl InteractionPlan {
let mut drop = 0;
let mut update = 0;
let mut create_index = 0;
let mut begin = 0;
let mut commit = 0;
let mut rollback = 0;
for interactions in &self.plan {
match interactions {
@ -321,6 +330,9 @@ impl InteractionPlan {
Query::Drop(_) => drop += 1,
Query::Update(_) => update += 1,
Query::CreateIndex(_) => create_index += 1,
Query::Begin(_) => begin += 1,
Query::Commit(_) => commit += 1,
Query::Rollback(_) => rollback += 1,
}
}
}
@ -333,6 +345,9 @@ impl InteractionPlan {
Query::Drop(_) => drop += 1,
Query::Update(_) => update += 1,
Query::CreateIndex(_) => create_index += 1,
Query::Begin(_) => begin += 1,
Query::Commit(_) => commit += 1,
Query::Rollback(_) => rollback += 1,
},
Interactions::Fault(_) => {}
}
@ -346,6 +361,9 @@ impl InteractionPlan {
create_count: create,
create_index_count: create_index,
drop_count: drop,
begin_count: begin,
commit_count: commit,
rollback_count: rollback,
}
}
}

View file

@ -7,6 +7,7 @@ use crate::{
query::{
predicate::Predicate,
select::{Distinctness, ResultColumn},
transaction::{Begin, Commit, Rollback},
Create, Delete, Drop, Insert, Query, Select,
},
table::SimValue,
@ -48,6 +49,8 @@ pub(crate) enum Property {
queries: Vec<Query>,
/// The select query
select: Select,
/// Interactive query information if any
interactive: Option<InteractiveQueryInfo>,
},
/// Double Create Failure is a property in which creating
/// the same table twice leads to an error.
@ -145,6 +148,12 @@ pub(crate) enum Property {
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InteractiveQueryInfo {
start_with_immediate: bool,
end_with_commit: bool,
}
impl Property {
pub(crate) fn name(&self) -> &str {
match self {
@ -168,6 +177,7 @@ impl Property {
row_index,
queries,
select,
interactive,
} => {
let (table, values) = if let Insert::Values { table, values } = insert {
(table, values)
@ -198,14 +208,26 @@ impl Property {
let assertion = Interaction::Assertion(Assertion {
message: format!(
"row [{:?}] not found in table {}",
"row [{:?}] not found in table {}, interactive={} commit={}, rollback={}",
row.iter().map(|v| v.to_string()).collect::<Vec<String>>(),
insert.table(),
interactive.is_some(),
interactive
.as_ref()
.map(|i| i.end_with_commit)
.unwrap_or(false),
interactive
.as_ref()
.map(|i| !i.end_with_commit)
.unwrap_or(false),
),
func: Box::new(move |stack: &Vec<ResultSet>, _: &SimulatorEnv| {
let rows = stack.last().unwrap();
match rows {
Ok(rows) => Ok(rows.iter().any(|r| r == &row)),
Ok(rows) => {
let found = rows.iter().any(|r| r == &row);
Ok(found)
}
Err(err) => Err(LimboError::InternalError(err.to_string())),
}
}),
@ -237,17 +259,17 @@ impl Property {
let table_name = create.table.name.clone();
let assertion = Interaction::Assertion(Assertion {
message:
"creating two tables with the name should result in a failure for the second query"
.to_string(),
func: Box::new(move |stack: &Vec<ResultSet>, _: &SimulatorEnv| {
let last = stack.last().unwrap();
match last {
Ok(_) => Ok(false),
Err(e) => Ok(e.to_string().to_lowercase().contains(&format!("table {table_name} already exists"))),
}
}),
});
message:
"creating two tables with the name should result in a failure for the second query"
.to_string(),
func: Box::new(move |stack: &Vec<ResultSet>, _: &SimulatorEnv| {
let last = stack.last().unwrap();
match last {
Ok(_) => Ok(false),
Err(e) => Ok(e.to_string().to_lowercase().contains(&format!("table {table_name} already exists"))),
}
}),
});
let mut interactions = Vec::new();
interactions.push(assumption);
@ -426,8 +448,8 @@ impl Property {
// If rows1 results have more than 1 column, there is a problem
if rows1.iter().any(|vs| vs.len() > 1) {
return Err(LimboError::InternalError(
"Select query without the star should return only one column".to_string(),
));
"Select query without the star should return only one column".to_string(),
));
}
// Count the 1s in the select query without the star
let rows1_count = rows1
@ -564,12 +586,26 @@ fn property_insert_values_select<R: rand::Rng>(
values: rows,
};
// Choose if we want queries to be executed in an interactive transaction
let interactive = if rng.gen_bool(0.5) {
Some(InteractiveQueryInfo {
start_with_immediate: rng.gen_bool(0.5),
end_with_commit: rng.gen_bool(0.5),
})
} else {
None
};
// Create random queries respecting the constraints
let mut queries = Vec::new();
// - [x] There will be no errors in the middle interactions. (this constraint is impossible to check, so this is just best effort)
// - [x] The inserted row will not be deleted.
// - [ ] The inserted row will not be updated. (todo: add this constraint once UPDATE is implemented)
// - [ ] The table `t` will not be renamed, dropped, or altered. (todo: add this constraint once ALTER or DROP is implemented)
if let Some(ref interactive) = interactive {
queries.push(Query::Begin(Begin {
immediate: interactive.start_with_immediate,
}));
}
for _ in 0..rng.gen_range(0..3) {
let query = Query::arbitrary_from(rng, (env, remaining));
match &query {
@ -593,6 +629,13 @@ fn property_insert_values_select<R: rand::Rng>(
}
queries.push(query);
}
if let Some(ref interactive) = interactive {
queries.push(if interactive.end_with_commit {
Query::Commit(Commit)
} else {
Query::Rollback(Rollback)
});
}
// Select the row
let select_query = Select {
@ -608,6 +651,7 @@ fn property_insert_values_select<R: rand::Rng>(
row_index,
queries,
select: select_query,
interactive,
}
}

View file

@ -10,7 +10,13 @@ use serde::{Deserialize, Serialize};
use turso_sqlite3_parser::to_sql_string::ToSqlContext;
use update::Update;
use crate::{model::table::SimValue, runner::env::SimulatorEnv};
use crate::{
model::{
query::transaction::{Begin, Commit, Rollback},
table::SimValue,
},
runner::env::SimulatorEnv,
};
pub mod create;
pub mod create_index;
@ -19,6 +25,7 @@ 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.
@ -31,6 +38,9 @@ pub(crate) enum Query {
Update(Update),
Drop(Drop),
CreateIndex(CreateIndex),
Begin(Begin),
Commit(Commit),
Rollback(Rollback),
}
impl Query {
@ -46,6 +56,7 @@ impl Query {
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> {
@ -58,6 +69,7 @@ impl Query {
| 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![],
}
}
@ -70,6 +82,9 @@ impl Query {
Query::Update(update) => update.shadow(env),
Query::Drop(drop) => drop.shadow(env),
Query::CreateIndex(create_index) => create_index.shadow(env),
Query::Begin(begin) => begin.shadow(env),
Query::Commit(commit) => commit.shadow(env),
Query::Rollback(rollback) => rollback.shadow(env),
}
}
}
@ -84,6 +99,9 @@ impl Display for Query {
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),
}
}
}

View file

@ -0,0 +1,52 @@
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::{model::table::SimValue, runner::env::SimulatorEnv};
#[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 Begin {
pub(crate) fn shadow(&self, _env: &mut SimulatorEnv) -> Vec<Vec<SimValue>> {
vec![]
}
}
impl Commit {
pub(crate) fn shadow(&self, _env: &mut SimulatorEnv) -> Vec<Vec<SimValue>> {
vec![]
}
}
impl Rollback {
pub(crate) fn shadow(&self, _env: &mut SimulatorEnv) -> Vec<Vec<SimValue>> {
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

@ -112,6 +112,18 @@ fn execute_query_rusqlite(
connection.execute(create_index.to_string().as_str(), ())?;
Ok(vec![])
}
Query::Begin(begin) => {
connection.execute(begin.to_string().as_str(), ())?;
Ok(vec![])
}
Query::Commit(commit) => {
connection.execute(commit.to_string().as_str(), ())?;
Ok(vec![])
}
Query::Rollback(rollback) => {
connection.execute(rollback.to_string().as_str(), ())?;
Ok(vec![])
}
}
}