Skip to content

Commit e52c1b3

Browse files
committed
Add async export progress callbacks with .NET parity
1 parent 64e1001 commit e52c1b3

12 files changed

Lines changed: 356 additions & 23 deletions

File tree

.github/workflows/rust.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ jobs:
318318
run: >-
319319
dotnet test dotnet-source/tests/MiniExcel.OpenXml.Tests/MiniExcel.OpenXml.Tests.csproj
320320
--framework net10.0
321-
--filter "FullyQualifiedName~MiniExcelOpenXmlExporterAsyncTests.SaveAsByAsyncEnumerable|FullyQualifiedName~MiniExcelOpenXmlExporterAsyncTests.SaveEmptyAsyncEnumerableTest"
321+
--filter "FullyQualifiedName~MiniExcelOpenXmlExporterAsyncTests.SaveAsByAsyncEnumerable|FullyQualifiedName~MiniExcelOpenXmlExporterAsyncTests.SaveEmptyAsyncEnumerableTest|FullyQualifiedName~MiniExcelOpenXmlExporterAsyncTests.ExportDataTableWithProgressTest"
322322
--verbosity minimal
323323
324324
async-template-parity:

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,13 @@ constant-memory writer runs. Producer errors, cancellation, dropped futures, val
155155
and destination races leave an existing target byte-identical or a missing target absent.
156156
`with_overwrite_file(true)` enables atomic replacement. The returned count excludes the header.
157157

158+
`save_as_with_schema_async_with_progress()` and `save_as_serialized_async_with_progress()` accept
159+
a thread-safe callback. It runs on the blocking worker with an increment of `1` after each data
160+
cell is written to the temporary workbook; header cells are excluded. The
161+
`*_with_cancellation_and_progress()` variants combine both controls. Progress is observational, not
162+
a commit guarantee: a later cancellation, validation error, or destination race can still roll back
163+
the workbook without retracting earlier reports.
164+
158165
## Borrowed Readers And Writers
159166

160167
Use visitor APIs for caller-owned `Read + Seek` sources without materializing all rows or transferring ownership:

README.zh-CN.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,12 @@ spool。Producer error、cancellation、drop future、validation failure 与 des
153153
都会使已有 target 字节不变,或使缺失 target 继续不存在。`with_overwrite_file(true)` 启用
154154
原子 replacement。返回 count 不含 header。
155155

156+
`save_as_with_schema_async_with_progress()``save_as_serialized_async_with_progress()` 接受
157+
thread-safe callback。每个 data cell 成功写入临时 workbook 后,blocking worker 会用增量 `1`
158+
调用 callback;header cell 不计入。`*_with_cancellation_and_progress()` variant 可同时使用
159+
cancellation 与 progress。Progress 只用于观察,不保证最终 commit:后续 cancellation、
160+
validation error 或 destination race 仍可回滚 workbook,已发出的报告不会撤销。
161+
156162
## 借用 Reader 与 Writer
157163

158164
对调用方持有的 `Read + Seek` source,可使用 visitor API 流式处理,不会物化全部 row 或转移所有权:

docs/compatibility.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ The latest `calamine 0.36` and `rust_xlsxwriter 0.97` require Rust 1.88. The MVP
5656
| `InsertSheet` append/replace | `insert()` / `insert_with_schema()` / `insert_serialized()` / borrowed reader-to-writer variants | Path APIs are atomic; separate borrowed streams require an empty sink and preserve package behavior without atomic commit |
5757
| Async Insert producer | `insert_with_schema_async*()` | Optional `async` feature; bounded producer channel with blocking XLSX work on a dedicated thread |
5858
| Async path query | `query_async*()` / `query_as_async*()` | Optional `async` feature; bounded dynamic/Serde streams, cooperative cancellation, blocking XLSX workers |
59-
| Async dynamic/Serde export | `save_as_with_schema_async*()` / `save_as_serialized_async*()` | Optional `async` feature; explicit or first-row-inferred schema, bounded producer, atomic destination, cooperative cancellation |
59+
| Async dynamic/Serde export | `save_as_with_schema_async*()` / `save_as_serialized_async*()` | Optional `async` feature; explicit or first-row-inferred schema, bounded producer, atomic destination, cooperative cancellation, data-cell progress |
6060
| Async basic template path output | `save_as_template_async*()` | Optional `async` feature; scalar/list renderer, atomic destination, cooperative cancellation |
6161
| Per-sheet visibility | `WriteOptions::with_sheet_visibility()` | Visible, hidden, and very hidden; first visible sheet is active |
6262
| `overwriteFile` | `WriteOptions::with_overwrite_file()` | Defaults to `false`; existing paths require explicit opt-in |
@@ -191,7 +191,7 @@ The contract covers only the current common surface: dynamic/typed path queries,
191191
| Addressed JSONL/Markdown/manifest RAG export | Rust research extension | No |
192192
| Async Insert producer | Implemented behind optional feature | Rust cancellation tests; not shared internals |
193193
| Async dynamic/typed path query | Implemented behind optional feature | Rust parity, cancellation, error, and cleanup tests |
194-
| Async dynamic/Serde path export | Implemented behind optional feature | Rust explicit/inferred schema, rollback, cancellation, and cleanup tests; pinned .NET async-enumerable baseline |
194+
| Async dynamic/Serde path export | Implemented behind optional feature | Rust explicit/inferred schema, rollback, cancellation, progress, and cleanup tests; pinned .NET async-enumerable/progress baseline |
195195
| Async basic template path output | Implemented behind optional feature | Rust success, rollback, cancellation, and cleanup tests; scoped pinned .NET async template baseline |
196196
| DataReader and broader stream ownership | Deferred | No |
197197
| Append worksheet to existing `.xlsx` workbook | Implemented and atomically committed | Rust tests; shared parity contract not yet extended |

docs/compatibility.zh-CN.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Rust MVP 在统一的 `MiniExcel` facade 后实现最小但实用的 MiniExcel
5656
| `InsertSheet` append/replace | `insert()` / `insert_with_schema()` / `insert_serialized()` / borrowed reader-to-writer variants | Path API 为原子操作;独立 borrowed stream 要求空 sink,并在无原子 commit 的情况下保持相同 package 行为 |
5757
| Async Insert producer | `insert_with_schema_async*()` | 可选 `async` feature;bounded producer channel,XLSX 工作在专用 blocking thread |
5858
| Async path query | `query_async*()` / `query_as_async*()` | 可选 `async` feature;bounded 动态/Serde stream、协作式 cancellation、blocking XLSX worker |
59-
| Async dynamic/Serde export | `save_as_with_schema_async*()` / `save_as_serialized_async*()` | 可选 `async` feature;显式或首行推断 schema、bounded producer、原子 destination、协作式 cancellation |
59+
| Async dynamic/Serde export | `save_as_with_schema_async*()` / `save_as_serialized_async*()` | 可选 `async` feature;显式或首行推断 schema、bounded producer、原子 destination、协作式 cancellation、data-cell progress |
6060
| Async 基础 template path output | `save_as_template_async*()` | 可选 `async` feature;scalar/list renderer、原子 destination、协作式 cancellation |
6161
| 每表 visibility | `WriteOptions::with_sheet_visibility()` | visible、hidden、very hidden;第一个 visible sheet 为 active |
6262
| `overwriteFile` | `WriteOptions::with_overwrite_file()` | 默认 `false`;已有路径需要显式允许覆盖 |
@@ -191,7 +191,7 @@ Rust workflow 会在 Linux 和 Windows 上运行 Rust 契约。其 .NET parity j
191191
| 带地址 JSONL/Markdown/manifest RAG 导出 | Rust 研究扩展 ||
192192
| Async Insert producer | 已通过可选 feature 实现 | Rust cancellation test;不共享内部行为 |
193193
| Async 动态/类型化 path query | 已通过可选 feature 实现 | Rust parity、cancellation、error 与 cleanup 测试 |
194-
| Async dynamic/Serde path export | 已通过可选 feature 实现 | Rust 显式/推断 schema、rollback、cancellation 与 cleanup 测试;固定 .NET async-enumerable 基准 |
194+
| Async dynamic/Serde path export | 已通过可选 feature 实现 | Rust 显式/推断 schema、rollback、cancellation、progress 与 cleanup 测试;固定 .NET async-enumerable/progress 基准 |
195195
| Async 基础 template path output | 已通过可选 feature 实现 | Rust success、rollback、cancellation 与 cleanup 测试;scoped 固定 .NET async template 基准 |
196196
| DataReader 与更广泛的 stream ownership | 延后 ||
197197
| 向现有 `.xlsx` workbook 追加 worksheet | 已实现并原子提交 | Rust 测试;共享 parity contract 尚未扩展 |

docs/dotnet-feature-gaps.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This report compares observable public capabilities in the local checkouts below
88

99
| Project | Revision |
1010
| --- | --- |
11-
| MiniExcel-Rust | working tree based on `436f1bf` (`0.3.0`) |
11+
| MiniExcel-Rust | working tree based on `64e1001` (`0.3.0`) |
1212
| MiniExcel .NET | `b9a76d7af62142e0e38545b6905b01a06e8d160e` |
1313

1414
The comparison uses the .NET public APIs, their controlling implementations, and focused tests under the sibling `../MiniExcel` checkout. Rust status is based on the public `MiniExcel` facade, options, integration tests, and [compatibility boundary](compatibility.md).
@@ -31,8 +31,8 @@ Rust already implements dynamic and Serde-typed XLSX path queries, inclusive A1
3131
| Named tables | Implemented | Dynamic/typed path queries, byte queries, and borrowed-reader visitors use table metadata headers and bounds with case-insensitive table-name matching. |
3232
| DataReader and DataTable | Different by design | Rust exposes iterators and borrowed visitors instead of .NET tabular interfaces. A Rust-native Arrow/record-batch adapter is deferred until a concrete integration requires it and does not block parity completion. |
3333
| Caller-owned streams | Partial | Borrowed synchronous dynamic/typed/structured visitors, metadata reads, dynamic/schema/typed/multi-sheet writers, and separate reader-to-writer Insert are implemented with leave-open semantics. Borrowed lazy iterators, borrowed async streams, and template streams remain unsupported. |
34-
| Async and cancellation | Partial | Optional runtime-neutral dynamic/Serde path queries, explicit-schema dynamic and inferred-schema Serde path export, basic template path output, and explicit-schema Insert support cooperative cancellation and atomic publication where applicable. Advanced template streams, borrowed async I/O, and progress callbacks remain unsupported. ZIP and filesystem work remains blocking on dedicated workers. |
35-
| General save inputs | Partial | Export from general objects/enumerables, dictionaries, `DataTable`, `IDataReader`, and async enumerables, with progress. Rust accepts dynamic or same-type Serde slices, bounded dynamic/Serde async streams, and reports per-sheet row counts. |
34+
| Async and cancellation | Partial | Optional runtime-neutral dynamic/Serde path queries, explicit-schema dynamic and inferred-schema Serde path export, basic template path output, and explicit-schema Insert support cooperative cancellation and atomic publication where applicable. Async XLSX exports report data-cell progress. Advanced template streams and borrowed async I/O remain unsupported. ZIP and filesystem work remains blocking on dedicated workers. |
35+
| General save inputs | Partial | Export from general objects/enumerables, dictionaries, `DataTable`, `IDataReader`, and async enumerables, with progress. Rust accepts dynamic or same-type Serde slices, bounded dynamic/Serde async streams with cell progress, and reports per-sheet row counts. |
3636
| Multi-sheet export | Partial | Rust creates ordered visible, hidden, and very-hidden worksheets, but does not yet accept heterogeneous Serde row types in one call. |
3737
| Existing-workbook operations | Implemented | Rust atomically appends, strictly replaces, renames, changes visibility, reorders, and performs .NET-style source-workbook copy-and-add while preserving unrelated package parts and worksheet identity. Rename preserves formula text; visibility rejects hiding the final visible sheet; reorder remaps active/view/local-name indices; copy-and-add preserves the source and atomically publishes a separate destination. |
3838
| Templates | Partial | Rust fills path/byte templates with scalars, arrays, conditional blocks, validated multirow groups, and `$=` formulas; path output also has a cancellable async wrapper. Streams, nested/logical conditions, grouped/conditional formulas, parametrized sheets, formula-reference translation, and formula calculation remain unsupported. Stale calcChain metadata is removed and full recalculation requested. |
@@ -52,7 +52,7 @@ Rust already implements dynamic and Serde-typed XLSX path queries, inclusive A1
5252
| --- | --- | --- |
5353
| Public read/write boundary | `miniexcel/src/facade.rs`, `miniexcel/src/options.rs` | `src/MiniExcel.OpenXml/Api/OpenXmlImporter.cs`, `OpenXmlExporter.cs` |
5454
| Async query | `MiniExcel::query_async*` and `query_as_async*`; Rust focused parity/cancellation/error/cleanup tests | `OpenXmlImporter.QueryAsync`; `MiniExcelOpenXmlImporterAsyncTests` |
55-
| Async export | `MiniExcel::save_as_with_schema_async*` and `save_as_serialized_async*`; Rust explicit/inferred schema, rollback/cancellation/cleanup tests | `OpenXmlExporter.ExportAsync`; `SaveAsByAsyncEnumerable` and empty async-enumerable tests |
55+
| Async export | `MiniExcel::save_as_with_schema_async*` and `save_as_serialized_async*`; Rust explicit/inferred schema, rollback/cancellation/progress/cleanup tests | `OpenXmlExporter.ExportAsync`; `SaveAsByAsyncEnumerable`, empty async-enumerable, and `ExportDataTableWithProgressTest` tests |
5656
| Async template | `MiniExcel::save_as_template_async*`; Rust focused rollback/cancellation/cleanup tests | `OpenXmlTemplater.SaveAsByTemplateAsync`; scoped basic/cancellation tests |
5757
| Template conditions | Enumerable-cell `@if`/`@elseif`/`@else` blocks; Rust sync/async branch/error/style tests | `TestIEnumerableConditional` |
5858
| Template groups | `@group`/`@header`/`@endgroup` multirow blocks; Rust sync/async order/error/style tests | `GroupTemplateTest`; `TestIEnumerableGrouped` |
@@ -71,7 +71,7 @@ The .NET APIs marked with `Async` also have generated synchronous counterparts t
7171

7272
## Suggested Implementation Order
7373

74-
1. **Borrowed async streams and progress**: add caller-owned async reader/writer integration and progress only where ownership, cancellation, and blocking-I/O semantics remain explicit.
74+
1. **Borrowed async streams**: add caller-owned async reader/writer integration only where ownership, cancellation, and blocking-I/O semantics remain explicit.
7575
2. **Advanced templates and collection mapping**: add formula/merge-aware groups, richer conditional expressions, parametrized sheets, and deterministic collection layouts through separate compatibility milestones.
7676
3. **Selected-sheet cloning**: not part of .NET `CopyAndAddSheet`; add only for a concrete Rust use case with a relationship-closure cloning contract.
7777

0 commit comments

Comments
 (0)