Repository navigation
Core: V4 write direction wrappers - #16936
Conversation
gaborkaszab
left a comment
There was a problem hiding this comment.
Hey @stevenzwu ,
I went through mostly for my own understanding. Left some comments, mostly I'm a bit hesitant to expose setting status and sequence numbers directly through the builders. Let me know what you think!
c78d6b1 to
828e0c7
Compare
828e0c7 to
fa86dc1
Compare
fa46d3d to
55f5573
Compare
b5c92a0 to
d5c696b
Compare
| } | ||
|
|
||
| @Test | ||
| void manifestTrackedFileAdapterFailsWhenAddedFilesCountMissing() { |
There was a problem hiding this comment.
Use manifestWithCounts? What about existing and deleted cases?
There was a problem hiding this comment.
Existing and deleted file counts are in InvalidCount, along with null and non-zero replaced and modified file counts. Row counts are not in that enum.
| assertThat(result.contentType()).isEqualTo(FileContent.DELETE_MANIFEST); | ||
| assertThat(result.formatVersion()).isZero(); | ||
| assertThat(result.recordCount()) | ||
| .isEqualTo(ADDED_FILES_COUNT + EXISTING_FILES_COUNT + DELETED_FILES_COUNT); |
There was a problem hiding this comment.
This uses a helper method to create manifest and then assumes what that helper passed for these counts. I'd rather see manifestWithCounts used with DELETES passed in so that this test can be read without skipping to see how manifests are written by writeManifestFile.
There was a problem hiding this comment.
The success test calls newManifestFile(content). The count constants live in the helper. I removed the multi-arg overload because the argument list was hard to read.
There was a problem hiding this comment.
This is still a problem. The test is not readable on its own and there's no need for this because the newManifestFile(FileContent) method is only called here. It should be embedded.
There was a problem hiding this comment.
Embedded the construction in manifestTrackedFileAdapter and removed newManifestFile, so the counts sit next to the assertions.
| ManifestFile adapted = TrackedFileAdapters.asManifestFile(original); | ||
| TrackedFile result = TrackedFileAdapters.forManifestFile().wrap(adapted); | ||
| assertThat(result).isSameAs(original); | ||
| assertThat(result.formatVersion()).isZero(); |
There was a problem hiding this comment.
Why does this pass and validate that the format version of the TrackedFile is 0? That seems odd to me. I think that a TrackedFile should typically use 4 when directly constructed.
I also think it is reasonable to drop this assertion entirely. It returns the original object.
There was a problem hiding this comment.
Dropped the format-version 0 check on the unwrap test, and that test now constructs the tracked file without passing version 0.
isZero() remains on manifestTrackedFileAdapter because GenericManifestFile does not override formatVersion() (the interface default is 0).
Add reusable write-direction wrappers that present a legacy DataFile, DeleteFile, or ManifestFile as a v4 TrackedFile row for manifest serialization. Content stats are served by a reusable map-backed view over the file's stat maps. Generated-by: Cursor Grok 4.6 Co-authored-by: Cursor <cursoragent@cursor.com>
Leave replaced counts as boxed zeros for consistency with other ManifestFile counters, unwrap already-adapted content files and manifest references, drop the formatVersion factory arg, and construct write wrappers from the TrackedFile writer schema instead of schema fragments. Report an absent partition or content stats as null rather than an empty struct, matching TrackedFileStruct. Newly written manifest-reference rows report format version 4; a rewrite of an already-adapted ManifestFile returns the original TrackedFile so a persisted 0 survives. Generated-by: Cursor Grok 4.6 Generated-by: Cursor Claude Opus 5 Co-authored-by: Cursor <cursoragent@cursor.com>
…e wrapped TrackedFile. The asManifestFile adapter inherited ManifestFile defaults, so a persisted format version and non-zero replaced counts were dropped on the ManifestFile view. Generated-by: Cursor Grok 4.6 Co-authored-by: Cursor <cursoragent@cursor.com>
Drop StructLike and write-schema from TrackedFileAdapters, add ManifestFile modified counts, and align MapBackedContentStats with review feedback. Generated-by: Cursor Grok 4.6 Co-authored-by: Cursor <cursoragent@cursor.com>
Reuse tracking wrappers when wrapping data and manifest files, implement StructLike on GeospatialBound, and treat pre-v4 manifests as Avro with EXISTING status. Generated-by: Cursor Grok 4.7 Co-authored-by: Cursor <cursoragent@cursor.com>
Geometry and geography bounds are already decoded by Conversions when given the column type, so decodeBound no longer needs a single-point special case. Generated-by: Cursor Grok 4.7 Co-authored-by: Cursor <cursoragent@cursor.com>
Look columns up by field id so field stats can be read from the maps without constructing the content-stats type first. Generated-by: Cursor Grok 4.7 Co-authored-by: Cursor <cursoragent@cursor.com>
Keep cached field stats when rebinding a file, and return null from a factory when the field has no stats struct. Look up the bound type by name. Generated-by: Cursor Grok 4.7 Co-authored-by: Cursor <cursoragent@cursor.com>
Assert mapped values instead of implementation details, matching review feedback on the v4 write-direction wrappers. Generated-by: Cursor Grok 4.7 Co-authored-by: Cursor <cursoragent@cursor.com>
The helpers are only used inside MapBackedContentStats. The manifest count errors stay valid for a null count. Generated-by: Cursor Grok 4.7 Co-authored-by: Cursor <cursoragent@cursor.com>
Cover manifest file-count failures in one parameterized test and fold the absent field-id case into field reuse, without repeating bound decoding. Generated-by: Cursor Grok 4.7 Co-authored-by: Cursor <cursoragent@cursor.com>
apache/main renamed Tracking.dvSnapshotId() to modifiedSnapshotId(). The write wrappers return null because manifest entries and manifest files have no such id. TrackedFileStruct also follows the partition field rename. Generated-by: Cursor Grok 4.7 Co-authored-by: Cursor <cursoragent@cursor.com>
apache/main moved formatVersion off TrackedFile onto manifest info. The write wrappers no longer implement it on TrackedFile; WrappedManifestInfo forwards the manifest's format version, and the manifest success test checks that forward. Generated-by: Cursor Grok 4.7 Co-authored-by: Cursor <cursoragent@cursor.com>
TrackedFile no longer carries equality_ids, so the write wrappers drop the equalityIds() overrides and size checks use totalBytes(). Generated-by: Cursor Grok 4.7
The row-count-only fixtures do not need the four null map arguments. Generated-by: Cursor Grok 4.7
| TrackedFile result = | ||
| TrackedFileAdapters.forDataFile(TABLE_SCHEMA) | ||
| .wrap( | ||
| newEntry() |
There was a problem hiding this comment.
newEntry returns an entry that uses a partitioned schema, but DATA_FILE uses the unpartitioned spec.
This is why in other test suites, I moved to using factory methods and constants that state whether they have content stats and/or partitioning. We should avoid mismatches here because we want test data that looks as close to real data as possible.
Also, does it make sense to have a newEntry() factory method? It looks like it's used here and in the next method. This suite is creating mismatched values because it is difficult for a reader to understand -- you have to go find newEntry to see what it's doing -- and that saves just a few lines. I would opt to have slightly longer tests that can be read and understood in one place.
There was a problem hiding this comment.
Inlined the construction at both call sites and removed the helper.
new GenericManifestEntry<DataFile>(
ManifestEntry.getSchema(UNPARTITIONED_SPEC.partitionType()).asStruct())
.wrapExisting(SNAPSHOT_ID, DATA_SEQUENCE_NUMBER, FILE_SEQUENCE_NUMBER, DATA_FILE)The reuse test uses the same construction with file2 (instead of DATA_FILE).
| } | ||
|
|
||
| @Test | ||
| void dataTrackedFileAdapterUnwrapsToOriginalTrackedFile() { |
There was a problem hiding this comment.
Would be better to co-locate these with the other tests for wrap, like dataTrackedFileAdapterRejectsNullFile.
There was a problem hiding this comment.
Moved only dataTrackedFileAdapterUnwrapsToOriginalTrackedFile next to dataTrackedFileAdapterRejectsNullFile.
manifestTrackedFileAdapterUnwrapsToOriginalTrackedFile stays with the other ManifestTrackedFile tests.
| assertThat(result.fileFormat()).isEqualTo(FileFormat.AVRO); | ||
| assertThat(result.tracking().status()).isEqualTo(EntryStatus.EXISTING); | ||
| assertThat(result.tracking().snapshotId()).isEqualTo(SNAPSHOT_ID); | ||
| assertThat(result.tracking().firstRowId()).isNull(); |
There was a problem hiding this comment.
Why is this null? Manifests should always have first row ID set after they have been written. If the status is EXISTING then this should have a non-null first row ID. It's also the more common case.
There was a problem hiding this comment.
A data manifest now uses FIRST_ROW_ID. A delete manifest stays null because a delete manifest's first_row_id is always null. The assertion follows the content.
There was a problem hiding this comment.
I think that this is correct
| assertThat(result.tracking().snapshotId()).isEqualTo(SNAPSHOT_ID); | ||
| assertThat(result.tracking().firstRowId()).isNull(); | ||
| assertThat(result.recordCount()) | ||
| .isEqualTo(ADDED_FILES_COUNT + EXISTING_FILES_COUNT + DELETED_FILES_COUNT); |
There was a problem hiding this comment.
It isn't obvious from reading this test that this is correct. Looks like this is also the only test where newManifestFile(FileContent) is called. That should be embedded. (I also replied on the previous thread.)
There was a problem hiding this comment.
Embedded the GenericManifestFile construction in manifestTrackedFileAdapter and removed newManifestFile helper.
rdblue
left a comment
There was a problem hiding this comment.
I think the tests look okay. I had a few comments but they are mostly about readability, not big issues.
The entry and manifest constructions sit next to the assertions they feed, and written data manifests keep a first row id. Generated-by: Cursor Grok 4.7
amogh-jahagirdar
left a comment
There was a problem hiding this comment.
Thanks @stevenzwu, I've been following the work offline and did another quick pass over it, seems right to me as well. I will go ahead and merge this; thanks @rdblue @gaborkaszab @anoopj @CTTY @anuragmantri for reviewing.
What
Adds v4 write-direction
TrackedFileadapters: reusable forwarding wrappers that present a legacy v2/v3ContentFile(DataFile/DeleteFile) as a v4 manifest-entry row on the write path, without materializing a fresh struct per row. A single adapter is allocated per writer and re-pointed at each file; content stats are served by a map-backed view (MapBackedContentStats) over the file's stat maps, withcopy()producing stable snapshots.Benchmark summary
JMH microbenchmarks compared two strategies for presenting a legacy file as a v4 row: convert (materialize a fresh struct per row) vs wrap (a reusable wrapper allocated once per writer and re-pointed per row, with zero per-row allocation). Throughput in ops/ms (higher is better); allocation via the GC profiler in B/op (lower is better).
Net: the reusable wrap model is adopted because it wins or ties everywhere and minimizes per-row allocation — the cost that grows with table width.
See the benchmark doc for full methodology and per-column numbers.