Repository navigation
pxf: DatasetReader ends a table only at the end of input or a directive (#164) - #167
Merged
Merged
Conversation
…ve (#164) A row cut short, an unterminated string, or anything else where a row could begin ended the row sequence cleanly, so an interrupted append read as a shorter, complete dataset. The reader now finds each row's end with the lexer Parse uses, as protowire-go's does: a row ends at its `)`, the sequence at a directive or the end of the input, and anything else is the error Parse reports for the same input, at its position. Also as protowire-go does: - the bytes held while a row's end is sought are capped at max_message_size; - Scan binds a row under the default max_message_size, since the body it binds repeats the column names and is wider than the row; - the header is found with the lexer too, stepping over @proto bodies, and a broken header is refused with Parse's error rather than "no @dataset directive in stream" or the byte scanner's own text. The byte scanner is gone. A token is taken as complete once the source has ended or four bytes follow it, since the lexer looks at most one byte past a token. The row fast path parses on the buffer and stops at the `)`, dropping comments before the `(`, which makes streaming a 300,000-row table about 15% faster than before. testdata/dataset-stream/go_sweep.tsv is protowire-go v1.16.1's result on 590 inputs (tools/dataset_stream_sweep): the spec fixtures, cut and broken rows and headers, the cap, Scan's limit, and the 4096-byte read boundary at every offset in 14 row shapes. cpp matches all 590; the reader before this change missed 219 of the first 576. The corpus test now reads the dataset fixtures through the reader alone and requires the error text Parse gives.
trendvidia
force-pushed
the
dataset-row-end-164
branch
from
November 7, 2026 03:01
feb90f0 to
a0af71d
Compare
This was referenced Nov 7, 2026
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.
Fixes #164. Built on #165 (#161, stream positions), whose positions the errors here depend on. #165 has merged, and this PR is rebased onto
main(1673cd9).What was wrong
DatasetReaderended a table cleanly at a row whose)never came, at an unterminated string, or at anything else where a row could begin. An interrupted append read as a shorter, complete dataset, and every row after a broken one was dropped.Parserefused the same inputs.What changes
The reader now finds each row's end with the lexer
Parseuses, as protowire-go's does. Before, it used its own byte scanner. A row ends at its), and the table ends at a directive or at the end of the input. Anything else is an error, and it is the errorParsegives for the same input, at the same position. Also as Go does:max_message_size(dataset input holds N bytes without a row boundary, MaxMessageSize=M). Before, a row that never closed was buffered whole.Scanbinds a row under the defaultmax_message_size. The body it binds repeats the column names, so it is wider than the row.Item 3 of the issue gave an example in which Go accepts at
max_message_size = 56. Measured, Go refuses that document: its first 4096-byte read already holds 99 bytes. The difference is real, but it shows only when the row arrives in a later read. The sweep has a case built that way (scan body wider than the cap, cap 4500): Go binds it, and the old cpp reader refused it.Also fixed
Parse's error. Before,Createsaidno @dataset directive in streamfor a header cut short, and gave the byte scanner's own text for an unterminated string. The cause is the same as the row bug, so the header scan was ported from Go as well. It steps over@protobodies. Measured against Go: 6 of 12 broken headers differed before, and all now match.). It drops the comments before the(instead of copying them.optscould be moved inCreate).The header fix is in the same commit as the rows: the two share the scan code in
dataset_reader.cc.How it decides a token is complete
cpp's lexer has no
sawEndflag. Go's has one, which says whether the lexer looked past the end of the buffer. Instead, a token counts as complete once the source has ended or four bytes follow it. The farthest the lexer looks past a token is one byte (1.5,1p8,µs, a/in a timestamp), so later bytes cannot change it. The sweep places the 4096-byte read boundary at every offset in and around 14 row shapes, under three caps. Every one of those cases matches Go.Evidence
testdata/dataset-stream/go_sweep.tsvholds protowire-go v1.16.1's (903b4a7) result on 590 inputs, written bytools/dataset_stream_sweep, whose header says how to regenerate it. TestDatasetReaderEnds.MatchProtowireGoreplays it. cpp matches all 590 (230 of them errors).src/includestashed, before the header cases were added) failed 219 of the 576 cases it had then. Those included the three fixtures, theScancase and the cap.PxfCorpus.Datasetnow reads every dataset fixture through the reader alone. Before, the harness rescanned the tail itself to get the verdict right. It also requires the streamed error text to equalParse's. All 43 fixtures pass.ctest: 730/730 locally (macOS, AppleClang). clang-format 18.1.8 is clean. clang-tidy 23 on the changed sources reports nothing new.performance-enum-sizeforRowScan. 50953c5 fixes that.Not done here
Scanpasses through options that Go'sScandrops (skip_validate,skip_post_decode,discard_unknown,on_secret_field,validator). cpp documentsskip_validate, so the fix needs your decision.mayEndToken. Every complete token before it is not lexed again. The old byte scanner rescanned the whole buffer on every read, so this is no worse than before.trendvidia/protowire-python#45 is blocked on this. I'll comment there when it merges.