diff --git a/tests/integration/common.rs b/tests/integration/common.rs index 1ef890db9..bf5eeaacb 100644 --- a/tests/integration/common.rs +++ b/tests/integration/common.rs @@ -38,6 +38,10 @@ impl TempDatabase { } pub fn new_with_rusqlite(table_sql: &str) -> Self { + let _ = tracing_subscriber::fmt() + .with_max_level(tracing::Level::TRACE) + .finish() + .try_init(); let mut path = TempDir::new().unwrap().into_path(); path.push("test.db"); { diff --git a/tests/integration/query_processing/test_read_path.rs b/tests/integration/query_processing/test_read_path.rs index 7f525f6a9..8617c4d19 100644 --- a/tests/integration/query_processing/test_read_path.rs +++ b/tests/integration/query_processing/test_read_path.rs @@ -3,7 +3,6 @@ use limbo_core::{OwnedValue, StepResult}; #[test] fn test_statement_reset_bind() -> anyhow::Result<()> { - let _ = env_logger::try_init(); let tmp_db = TempDatabase::new_with_rusqlite("create table test (i integer);"); let conn = tmp_db.connect_limbo(); @@ -48,7 +47,6 @@ fn test_statement_reset_bind() -> anyhow::Result<()> { #[test] fn test_statement_bind() -> anyhow::Result<()> { - let _ = env_logger::try_init(); let tmp_db = TempDatabase::new_with_rusqlite("create table test (i integer);"); let conn = tmp_db.connect_limbo(); @@ -99,3 +97,152 @@ fn test_statement_bind() -> anyhow::Result<()> { } Ok(()) } + +#[test] +fn test_insert_parameter_remap() -> anyhow::Result<()> { + // ─────────────────────── schema ────────────────────────────── + // Table a b c d + // INSERT lists: d , c , a , b + // VALUES list: 22 , ?1 , 7 , ?2 + // + // Expected row on disk: a = 7 , b = ?2 , c = ?1 , d = 22 + // + // We bind ?1 = 111 and ?2 = 222 and expect (7,222,111,22). + // ─────────────────────────────────────────────────────────────── + + let tmp_db = TempDatabase::new_with_rusqlite( + "create table test (a integer, b integer, c integer, d integer);", + ); + let conn = tmp_db.connect_limbo(); + + // prepare INSERT with re-ordered columns and constants + let mut ins = conn.prepare("insert into test (d, c, a, b) values (22, ?, 7, ?);")?; + let args = [OwnedValue::Integer(111), OwnedValue::Integer(222)]; + for (i, arg) in args.iter().enumerate() { + let idx = i + 1; + ins.bind_at(idx.try_into()?, arg.clone()); + } + loop { + match ins.step()? { + StepResult::IO => tmp_db.io.run_once()?, + StepResult::Done | StepResult::Interrupt => break, + StepResult::Busy => panic!("database busy"), + _ => {} + } + } + + let mut sel = conn.prepare("select a, b, c, d from test;")?; + loop { + match sel.step()? { + StepResult::Row => { + let row = sel.row().unwrap(); + // insert_index = 3 + // A = 7 + assert_eq!(row.get::<&OwnedValue>(0).unwrap(), &OwnedValue::Integer(7)); + // insert_index = 4 + // B = 222 + assert_eq!( + row.get::<&OwnedValue>(1).unwrap(), + &OwnedValue::Integer(222) + ); + // insert_index = 2 + // C = 111 + assert_eq!( + row.get::<&OwnedValue>(2).unwrap(), + &OwnedValue::Integer(111) + ); + // insert_index = 1 + // D = 22 + assert_eq!(row.get::<&OwnedValue>(3).unwrap(), &OwnedValue::Integer(22)); + } + StepResult::IO => tmp_db.io.run_once()?, + StepResult::Done | StepResult::Interrupt => break, + StepResult::Busy => panic!("database busy"), + } + } + + // exactly two distinct parameters were used + assert_eq!(ins.parameters().count(), 2); + + Ok(()) +} + +#[test] +fn test_insert_parameter_remap_all_params() -> anyhow::Result<()> { + // ─────────────────────── schema ────────────────────────────── + // Table a b c d + // INSERT lists: d , a , c , b + // VALUES list: ?1 , ?2 , ?3 , ?4 + // + // Expected row on disk: a = ?2 , b = ?4 , c = ?3 , d = ?1 + // + // We bind ?1 = 999, ?2 = 111, ?3 = 333, ?4 = 444. + // The row should be (111, 444, 333, 999). + // ─────────────────────────────────────────────────────────────── + + let tmp_db = TempDatabase::new_with_rusqlite( + "create table test (a integer, b integer, c integer, d integer);", + ); + let conn = tmp_db.connect_limbo(); + let mut ins = conn.prepare("insert into test (d, a, c, b) values (?, ?, ?, ?);")?; + + let values = [ + OwnedValue::Integer(999), // ?1 → d + OwnedValue::Integer(111), // ?2 → a + OwnedValue::Integer(333), // ?3 → c + OwnedValue::Integer(444), // ?4 → b + ]; + for (i, value) in values.iter().enumerate() { + let idx = i + 1; + ins.bind_at(idx.try_into()?, value.clone()); + } + + // execute the insert (no rows returned) + loop { + match ins.step()? { + StepResult::IO => tmp_db.io.run_once()?, + StepResult::Done | StepResult::Interrupt => break, + StepResult::Busy => panic!("database busy"), + _ => {} + } + } + + let mut sel = conn.prepare("select a, b, c, d from test;")?; + loop { + match sel.step()? { + StepResult::Row => { + let row = sel.row().unwrap(); + + // insert_index = 2 + // A = 111 + assert_eq!( + row.get::<&OwnedValue>(0).unwrap(), + &OwnedValue::Integer(111) + ); + // insert_index = 4 + // B = 444 + assert_eq!( + row.get::<&OwnedValue>(1).unwrap(), + &OwnedValue::Integer(444) + ); + // insert_index = 3 + // C = 333 + assert_eq!( + row.get::<&OwnedValue>(2).unwrap(), + &OwnedValue::Integer(333) + ); + // insert_index = 1 + // D = 999 + assert_eq!( + row.get::<&OwnedValue>(3).unwrap(), + &OwnedValue::Integer(999) + ); + } + StepResult::IO => tmp_db.io.run_once()?, + StepResult::Done | StepResult::Interrupt => break, + StepResult::Busy => panic!("database busy"), + } + } + assert_eq!(ins.parameters().count(), 4); + Ok(()) +}