From 6264d694d5438fddf08a6365eb0b6cc023b9dead Mon Sep 17 00:00:00 2001 From: pedrocarlo Date: Wed, 10 Sep 2025 15:49:42 -0300 Subject: [PATCH] on reprepare create new state with updated number of cursors and registers, so that the Program insns are in sync with ProgramState --- .gitignore | 3 +- Cargo.lock | 2 +- core/lib.rs | 16 +++- core/types.rs | 12 +++ core/vdbe/mod.rs | 24 ++++- .../query_processing/test_multi_thread.rs | 95 ++++++++++++++++++- 6 files changed, 147 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 294d5a6dc..61a87d17e 100644 --- a/.gitignore +++ b/.gitignore @@ -44,4 +44,5 @@ profile.json.gz simulator-output/ &1 -bisected.sql \ No newline at end of file +bisected.sql +*.log \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index c44ea4996..6a024d184 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4424,7 +4424,7 @@ dependencies = [ [[package]] name = "turso_whopper" -version = "0.1.5" +version = "0.2.0-pre.1" dependencies = [ "anyhow", "clap", diff --git a/core/lib.rs b/core/lib.rs index 8a2167e7c..74b20e48a 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -2078,6 +2078,8 @@ pub struct Statement { /// indicates if the statement is a NORMAL/EXPLAIN/EXPLAIN QUERY PLAN query_mode: QueryMode, + /// Flag to show if the statement ran to completion + done: bool, } impl Statement { @@ -2103,6 +2105,7 @@ impl Statement { pager, accesses_db, query_mode, + done: false, } } pub fn get_query_mode(&self) -> QueryMode { @@ -2158,6 +2161,7 @@ impl Statement { if matches!(res, Ok(StepResult::Done)) { let mut conn_metrics = self.program.connection.metrics.borrow_mut(); conn_metrics.record_statement(self.state.metrics.clone()); + self.done = true; } res @@ -2219,7 +2223,8 @@ impl Statement { }; // Save parameters before they are reset let parameters = std::mem::take(&mut self.state.parameters); - self.reset(); + self.state = + vdbe::ProgramState::new(self.program.max_registers, self.program.cursor_ref.len()); // Load the parameters back into the state self.state.parameters = parameters; Ok(()) @@ -2320,11 +2325,20 @@ impl Statement { pub fn reset(&mut self) { self.state.reset(); + self.done = false; } pub fn row(&self) -> Option<&Row> { self.state.result_row.as_ref() } + + pub fn get_sql(&self) -> &str { + &self.program.sql + } + + pub fn is_done(&self) -> bool { + self.done + } } pub type Row = vdbe::Row; diff --git a/core/types.rs b/core/types.rs index ef66086fe..2381bdaae 100644 --- a/core/types.rs +++ b/core/types.rs @@ -2338,6 +2338,18 @@ pub enum Cursor { MaterializedView(Box), } +impl Debug for Cursor { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::BTree(..) => f.debug_tuple("BTree").finish(), + Self::Pseudo(..) => f.debug_tuple("Pseudo").finish(), + Self::Sorter(..) => f.debug_tuple("Sorter").finish(), + Self::Virtual(..) => f.debug_tuple("Virtual").finish(), + Self::MaterializedView(..) => f.debug_tuple("MaterializedView").finish(), + } + } +} + impl Cursor { pub fn new_btree(cursor: BTreeCursor) -> Self { Self::BTree(Box::new(cursor)) diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 6f28fe745..2546a951d 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -387,7 +387,29 @@ impl ProgramState { self.parameters.clear(); self.current_collation = None; #[cfg(feature = "json")] - self.json_cache.clear() + self.json_cache.clear(); + + // Reset state machines + self.op_delete_state = OpDeleteState { + sub_state: OpDeleteSubState::MaybeCaptureRecord, + deleted_record: None, + }; + self.op_idx_delete_state = None; + self.op_integrity_check_state = OpIntegrityCheckState::Start; + self.metrics = StatementMetrics::new(); + self.op_open_ephemeral_state = OpOpenEphemeralState::Start; + self.op_new_rowid_state = OpNewRowidState::Start; + self.op_idx_insert_state = OpIdxInsertState::MaybeSeek; + self.op_insert_state = OpInsertState { + sub_state: OpInsertSubState::MaybeCaptureRecord, + old_record: None, + }; + self.op_no_conflict_state = OpNoConflictState::Start; + self.seek_state = OpSeekState::Start; + self.current_collation = None; + self.op_column_state = OpColumnState::Start; + self.op_row_id_state = OpRowIdState::Start; + self.view_delta_state = ViewDeltaCommitState::NotStarted; } pub fn get_cursor(&mut self, cursor_id: CursorID) -> &mut Cursor { diff --git a/tests/integration/query_processing/test_multi_thread.rs b/tests/integration/query_processing/test_multi_thread.rs index 4450e5d2a..e159d43c4 100644 --- a/tests/integration/query_processing/test_multi_thread.rs +++ b/tests/integration/query_processing/test_multi_thread.rs @@ -1,6 +1,6 @@ use std::sync::{atomic::AtomicUsize, Arc}; -use turso_core::StepResult; +use turso_core::{Statement, StepResult}; use crate::common::{maybe_setup_tracing, TempDatabase}; @@ -234,3 +234,96 @@ fn test_schema_reprepare_write() { } } } + +fn advance(stmt: &mut Statement) -> anyhow::Result<()> { + stmt.step()?; + stmt.run_once()?; + Ok(()) +} + +/// Regression test detected by whopper +#[test] +fn test_interleaved_transactions() -> anyhow::Result<()> { + maybe_setup_tracing(); + let tmp_db = TempDatabase::new_empty(true); + { + let bootstrap_conn = tmp_db.connect_limbo(); + bootstrap_conn.execute("CREATE TABLE table_0 (id INTEGER,col_1 REAL,col_2 INTEGER,col_3 REAL,col_4 TEXT,col_5 REAL,col_6 TEXT)")?; + bootstrap_conn.execute("CREATE INDEX idx_table_0_0 ON table_0 (col_4 ASC)")?; + bootstrap_conn.execute("CREATE INDEX idx_table_0_1 ON table_0 (id DESC)")?; + bootstrap_conn.execute("CREATE INDEX idx_table_0_2 ON table_0 (col_5 DESC)")?; + bootstrap_conn.close()?; + } + + let conn = [ + tmp_db.connect_limbo(), + tmp_db.connect_limbo(), + tmp_db.connect_limbo(), + tmp_db.connect_limbo(), + ]; + + let mut statement2 = conn[2].prepare("BEGIN")?; + let mut statement0 = conn[0].prepare("BEGIN")?; + let mut statement1 = conn[1].prepare("BEGIN")?; + + advance(&mut statement2)?; + + let mut statement2 = conn[2].prepare("DELETE FROM table_0 WHERE (TRUE)")?; + + advance(&mut statement0)?; + + let mut statement0 = conn[0].prepare("COMMIT")?; + + advance(&mut statement1)?; + + let mut statement1 = conn[1].prepare("UPDATE table_0 SET col_5 = -2926216022.864461, col_1 = 1136343846.3760414, col_2 = 1260332354248861058, col_6 = 'breathtaking_wallace', col_3 = -8354252674.968108, id = -2763965266862900284 WHERE (TRUE)")?; + + advance(&mut statement2)?; + advance(&mut statement0)?; + advance(&mut statement1)?; + advance(&mut statement2)?; + + let mut statement2 = conn[2].prepare("COMMIT")?; + + advance(&mut statement1)?; + advance(&mut statement2)?; + + let mut statement2 = conn[2].prepare("BEGIN")?; + + let mut statement3 = conn[3].prepare("BEGIN")?; + + advance(&mut statement1)?; + advance(&mut statement2)?; + + let mut statement2 = conn[2].prepare("INSERT INTO table_0 VALUES (3433031730186055493, -9049649117.499245, 377748201198469116, -303828055.307354, 'hardworking_pinotnoir', 3130880977.346573, 'dazzling_identity'), (4047491512698975530, -6415241771.805258, 8252804953477887816, 6468710871.6649, 'diplomatic_karamazov', 590358226.8343716, 'hilarious_strasbourg'), (2865599543545078376, 84894401.22016525, 9113810426850381627, -1136160051.7521439, 'funny_benton', 9522389352.598354, 'magnificent_french'), (-157899885850804353, -303833796.57147026, 486259919370287064, -9427424128.005714, 'considerate_diamant', -3105334243.936157, 'kind_walker'), (8502247374804763489, -2126532888.2616653, 5690470012873526939, 4011656749.2326107, 'kind_ladrido', 1381034902.7760563, 'humorous_crane'), (2487742055507017334, -5830452441.986847, 3661939929057695925, -6299976423.211256, 'adaptable_raevsky', -7871748970.666381, 'technological_bataille'), (-5348095239593101865, -998225440.4524403, 5195262288395229508, -8305444803.975374, 'upbeat_courtney', -3943473497.4281626, 'imaginative_arrigoni'), (-6470080787150464674, -5833281407.383408, 5877012236478010308, 3023123550.254177, 'fearless_cairo', -141073531.91679573, 'generous_university')")?; + + advance(&mut statement3)?; + + let mut statement3 = conn[3].prepare("INSERT INTO table_0 VALUES (-7468459471409934075, 8179435779.870651, -7868006515434924912, -5415470506.527203, 'affectionate_n1x', -88866295.57206345, 'agreeable_treloar'), (3000445982321368777, 3099814982.0727863, -5101787605795972474, -925278326.7265358, 'giving_individualiste', -6553332857.366568, 'brave_patrizia'), (406163996859206098, -3340292138.289094, -5058217201699339610, 2605267874.8582096, 'fabulous_burnett', -4601912326.914466, 'super_jedi'), (6398781934600428549, -6770226564.882048, -2332649333251794167, 6904161964.055864, 'shining_sergent', -4779129294.073781, 'hardworking_beggar'), (1530150677936272307, -8683321096.443897, -2211014401610293017, 2417417840.8996468, 'magnificent_datacide', -2218929107.793541, 'ravishing_nw'), (8028216547992752413, -8876487798.088352, 8974386493479719872, -6723037189.199554, 'glimmering_murray', -1973499548.0633707, 'spectacular_fitzpatrick')")?; + + advance(&mut statement1)?; + + let mut statement1 = conn[1].prepare( + "CREATE INDEX idx_table_0_persiste ON table_0 (col_2 ASC, col_6 ASC, col_3 ASC)", + )?; + + advance(&mut statement2)?; + advance(&mut statement3)?; + + let mut statement0 = conn[0].prepare("BEGIN")?; + + advance(&mut statement1)?; + assert!(statement1.is_done()); + + let mut statement1 = conn[1].prepare("COMMIT")?; + + advance(&mut statement2)?; + advance(&mut statement0)?; + + let mut _statement0 = conn[0].prepare("UPDATE table_0 SET col_3 = 7634893024.5729065, col_6 = 'glimmering_besnard', col_1 = 9240915430.267292, col_4 = 'efficient_mekan' WHERE (TRUE)")?; + + advance(&mut statement1)?; + advance(&mut statement2)?; + + Ok(()) +}