From 1d83bb48c533b9e56c04f383cb2176964292312c Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Wed, 30 Jul 2025 17:27:15 +0530 Subject: [PATCH 1/2] fix exec_concat_ws implementation --- core/vdbe/execute.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/core/vdbe/execute.rs b/core/vdbe/execute.rs index a9f21431e..b0d919ae9 100644 --- a/core/vdbe/execute.rs +++ b/core/vdbe/execute.rs @@ -7464,24 +7464,21 @@ fn exec_concat_ws(registers: &[Register]) -> Value { return Value::Null; } - let separator = match ®isters[0].get_owned_value() { + let separator = match registers[0].get_owned_value() { Value::Null | Value::Blob(_) => return Value::Null, v => format!("{v}"), }; - let mut result = String::new(); - for (i, reg) in registers.iter().enumerate().skip(1) { - if i > 1 { - result.push_str(&separator); - } - match reg.get_owned_value() { - v if matches!(v, Value::Text(_) | Value::Integer(_) | Value::Float(_)) => { - result.push_str(&format!("{v}")) + let parts = registers[1..] + .iter() + .filter_map(|reg| match reg.get_owned_value() { + Value::Text(_) | Value::Integer(_) | Value::Float(_) => { + Some(format!("{}", reg.get_owned_value())) } - _ => continue, - } - } + _ => None, + }); + let result = parts.collect::>().join(&separator); Value::build_text(result) } From be36fe12c6613ac9e18b65a6858929ac5b637263 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Wed, 30 Jul 2025 17:54:51 +0530 Subject: [PATCH 2/2] add fuzz test for concat_ws --- tests/integration/fuzz/mod.rs | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/integration/fuzz/mod.rs b/tests/integration/fuzz/mod.rs index 66b7d2058..31f1d75c4 100644 --- a/tests/integration/fuzz/mod.rs +++ b/tests/integration/fuzz/mod.rs @@ -1508,6 +1508,51 @@ mod tests { } } + #[test] + fn concat_ws_fuzz() { + let _ = env_logger::try_init(); + + let (mut rng, seed) = rng_from_time(); + log::info!("seed: {seed}"); + + for _ in 0..100 { + let db = TempDatabase::new_empty(false); + let limbo_conn = db.connect_limbo(); + let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap(); + + let num_args = rng.random_range(7..=17); + let mut args = Vec::new(); + for _ in 0..num_args { + let arg = match rng.random_range(0..3) { + 0 => rng.random_range(-100..100).to_string(), + 1 => format!( + "'{}'", + (0..rng.random_range(1..=5)) + .map(|_| rng.random_range(b'a'..=b'z') as char) + .collect::() + ), + 2 => "NULL".to_string(), + _ => unreachable!(), + }; + args.push(arg); + } + + let sep = match rng.random_range(0..=2) { + 0 => "','", + 1 => "'-'", + 2 => "NULL", + _ => unreachable!(), + }; + + let query = format!("SELECT concat_ws({}, {})", sep, args.join(", ")); + + let limbo = limbo_exec_rows(&db, &limbo_conn, &query); + let sqlite = sqlite_exec_rows(&sqlite_conn, &query); + + assert_eq!(limbo, sqlite, "seed: {seed}, sep: {sep}, args: {args:?}"); + } + } + #[test] // Simple fuzz test for TOTAL with mixed numeric/non-numeric values pub fn total_agg_fuzz() {