feat(write): write csv through TabularWriter, round-tripping through the import - #77
Merged
Merged
Conversation
…trips through import
…chive's streamed ods
…s with an async-only target
…t would not give back
…n formula guard for csv
…man and US cultures
…t it would split into records, and leave headers unguarded
…complete after completing, close the target if disposal fails
…the import reads back
…he reader would split
Open
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
Closes #70. Part 1 of 5 of #67: writing tabular files, starting with csv. A caller writes a table row by row through
TabularWriterinto any stream — a file, a blob, an HTTP response body — and importing the file throughTabularImportergives back the same values with the same types.Spec:
docs/superpowers/specs/2026-10-03-writing-design.md. Plan:docs/superpowers/plans/2026-10-03-writing-part-1-csv.md.TabularWriter(format-agnostic):BeginSheet/BeginRow/ typedWriteoverloads (string?,long,decimal,double,DateTime,DateOnly,bool) /WriteEmpty/EndRow, thenCompleteAsync. Writes go synchronously into a pooled in-memory buffer; onlyFlushAsync,CompleteAsyncandDisposeAsynctouch the target, always asynchronously, so it serves an ASP.NET Core response body. Flush wheneverFlushRecommendedis true and memory stays flat in the row count. A file is valid only afterCompleteAsync; any exception faults the writer.,, ISO dates, UTF-8 with BOM), or a culture (de-DE→;,1234,5,03.10.2026). A culture is accepted only if a probe of its numbers and dates, written as the writer writes them, reads back through the import's ownValueReading. Every delimiter the reader's dialect detector could pick is quoted, so a value can never sway detection. Opt-inFormulaGuardagainst csv injection.TabularWriteExceptioncarrying a code and the sheet, row and column:write.precision-loss(a double past 15 significant digits),write.not-finite,write.date-out-of-range(year 1),write.text-too-long,write.too-many-lines,write.ambiguous-line-breaks(multi-line text the reader's stray-quote recovery would split into records),write.invalid-character(characters XML 1.0 forbids — one rule for every format). The documented exceptions — trimmed text, milliseconds,DateTime.Kind,DateOnly→DateTime— are in KNOWN-ISSUES.Performance. Not yet benchmarked formally (part 5, #74). A spot check in Release, 6 columns (long, quoted text, decimal, date-time, bool, double) to
Stream.Null: about 260 ns per row, about 0.5 s per million rows; about 97 KB allocated whether 100k or 1M rows are written — nothing per row (values are formatted withTryFormatinto a reused buffer). The read path is unchanged.Tests: round trips through the import under the invariant, de-DE and en-US cultures (every value kind and its edge values); a seeded round-trip fuzz (3 cultures × 6 layouts × 300 rows) asserting that whatever the writer accepts the import reads back; a target stream that refuses every synchronous operation; stream ownership, faulting, cancellation, misuse. The fuzz fails when either csv fix from the final review is reverted.
Known follow-up, reader side: when the 64 KB dialect probe ends inside a multi-line quoted field, the detector stops honouring quotes.
Checklist
dotnet buildanddotnet testpass on net8.0 and net10.0 with zero warnings. (2126 tests)ErrorCodesand the guide's error-code table agree.TabularWriter,TabularWriterOptions,WriteColumn,CsvWriterOptions,TabularWriteException,ErrorCodes.Write)src/.