From 63a44438503ecede6b363f4a61188b5b83c6eb1c Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Tue, 28 Jul 2026 08:41:26 -0400 Subject: [PATCH 1/2] Fix division by zero Co-Authored-By: Claude Opus 5 --- src/transparent.rs | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/transparent.rs b/src/transparent.rs index d863bf7..688f57a 100644 --- a/src/transparent.rs +++ b/src/transparent.rs @@ -807,7 +807,9 @@ fn maintenance_for_todo( }; let mut total_updated: i64 = 0; - let mut chunk_size = config.incremental_compression_step_bytes / avg_sample_bytes; + // avg_sample_bytes can be zero when every sample of this group is empty (e.g. '' or x''), + // so clamp it to avoid a division by zero + let mut chunk_size = config.incremental_compression_step_bytes / avg_sample_bytes.max(1); if chunk_size < 1 { chunk_size = 1; } @@ -883,7 +885,7 @@ fn maintenance_for_todo( pretty_bytes(todo.total_bytes), pretty_bytes(total_size_after), pretty_bytes(avg_sample_bytes), - pretty_bytes(total_size_after / total_count_after), + pretty_bytes(total_size_after / total_count_after.max(1)), ); } Ok(total_updated) @@ -1252,6 +1254,42 @@ mod tests { Ok(()) } + /// Regression test: a group where every value is empty has an average sample size of zero, + /// which used to cause a division by zero when computing the chunk size. + #[test] + fn empty_values_do_not_divide_by_zero() -> anyhow::Result<()> { + let db = create_example_db(None, 0)?; + + for id in 1..=10 { + db.execute( + "insert into events (id, timestamp, data) values (?, '2020-01-01', '')", + params![id], + )?; + } + + db.query_row( + r#"select zstd_enable_transparent(?)"#, + params![r#"{"table": "events", "column": "data", "compression_level": 3, "dict_chooser": "'[nodict]'"}"#], + |_| Ok(()) + ).context("enable transparent")?; + + db.query_row( + "select zstd_incremental_maintenance(9999999, 1)", + params![], + |row| row.get::<_, i64>(0), + ) + .context("incremental maintenance")?; + + let uncompressed_count: i64 = db.query_row( + "select count(*) from _events_zstd where _data_dict is null", + params![], + |row| row.get(0), + )?; + assert_eq!(uncompressed_count, 0); + + Ok(()) + } + #[test] #[should_panic(expected = "another_col (another_col_idx) - used as part of index")] fn indexed_column_cannot_be_enabled() { From 499c5e8622f0bae862144024eb15de714f5a0093 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Tue, 28 Jul 2026 13:23:55 -0400 Subject: [PATCH 2/2] Fix clippy warnings --- src/transparent.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/transparent.rs b/src/transparent.rs index 688f57a..9e5c3dc 100644 --- a/src/transparent.rs +++ b/src/transparent.rs @@ -195,7 +195,7 @@ pub fn zstd_enable_transparent<'a>(ctx: &Context) -> anyhow::Result(ctx: &Context) -> anyhow::Result