From c6ecd9808f85f445524585a1897fe5399485f578 Mon Sep 17 00:00:00 2001 From: zipg Date: Mon, 14 Sep 2026 22:26:38 +0800 Subject: [PATCH] fix(export): enable ZIP64 for large XLSX exports --- crates/dbx-core/src/xlsx_export.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/crates/dbx-core/src/xlsx_export.rs b/crates/dbx-core/src/xlsx_export.rs index 82ce88131a..b7c429603d 100644 --- a/crates/dbx-core/src/xlsx_export.rs +++ b/crates/dbx-core/src/xlsx_export.rs @@ -238,7 +238,10 @@ fn push_data_row_xml( /// `inlineStr` cell encoding is highly repetitive, so Deflate typically shrinks /// the file several-fold over `Stored` (matching what Excel/Navicat produce). fn xlsx_zip_options() -> zip::write::SimpleFileOptions { - zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated) + // Table exports can exceed the ZIP32 4 GiB entry limit even when the + // result is streamed row by row. Enable ZIP64 up front so the writer does + // not fail after a long export has already queried the database. + zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated).large_file(true) } fn write_zip_entry(zip: &mut zip::ZipWriter, path: &str, content: &str) -> Result<(), String> { @@ -1226,7 +1229,7 @@ mod tests { use calamine::{open_workbook_auto, Reader}; use serde_json::{json, Value}; use std::fs; - use std::io::{Read, Write}; + use std::io::{Cursor, Read, Write}; #[derive(Default)] struct WriteStats { @@ -1257,6 +1260,20 @@ mod tests { content } + #[test] + fn xlsx_zip_entries_enable_zip64_for_large_exports() { + let mut zip = zip::ZipWriter::new(Cursor::new(Vec::new())); + zip.start_file("large-entry.xml", super::xlsx_zip_options()).expect("start ZIP64 entry"); + zip.write_all(b"test").expect("write ZIP64 entry"); + let bytes = zip.finish().expect("finish ZIP64 archive").into_inner(); + + assert_eq!(&bytes[..4], b"PK\x03\x04"); + let name_len = u16::from_le_bytes([bytes[26], bytes[27]]) as usize; + let extra_len = u16::from_le_bytes([bytes[28], bytes[29]]) as usize; + let extra_data = &bytes[30 + name_len..30 + name_len + extra_len]; + assert!(extra_data.windows(2).any(|tag| tag == [0x01, 0x00])); + } + /// Assert every entry in the XLSX (ZIP) buffer is Deflate-compressed, which /// is what keeps exported workbooks small (see `xlsx_zip_options`). fn assert_all_entries_deflated(bytes: &[u8]) {