Repository navigation
Conversation
51e06e3 to
ee6b97b
Compare
ee6b97b to
0073109
Compare
0073109 to
63c4057
Compare
gaborkaszab
left a comment
There was a problem hiding this comment.
Thanks for keeping this rolling, @stevenzwu !
I have 2 comments in general:
- I think I'd not include metadata tables to this PR. That seems an orthogonal question and can be a follow-up and a different discussion how to visualize this abstraction
- For me the abstraction layer we introduce here is too specific. I describe this in multiple of my comments, but basically we could simplify encryption APIs if we introduced a more general abstraction here that goes for every file that keeps it's encrypted encryption keys in
TableMetadata.encryptionKeys, includingTableStataisticsandPartitionStatistics(see this for the former)
|
This pull request has been marked as stale due to 30 days of inactivity. It will be closed in 1 week if no further activity occurs. If you think that’s incorrect or this pull request requires a review, please simply write any comment. If closed, you can revive the PR at any time and @mention a reviewer or discuss it on the dev@iceberg.apache.org list. Thank you for your contributions. |
81159ce to
c9b805a
Compare
89ffa8e to
9b1909b
Compare
|
This pull request has been marked as stale due to 30 days of inactivity. It will be closed in 1 week if no further activity occurs. If you think that’s incorrect or this pull request requires a review, please simply write any comment. If closed, you can revive the PR at any time and @mention a reviewer or discuss it on the dev@iceberg.apache.org list. Thank you for your contributions. |
|
This pull request has been closed due to lack of activity. This is not a judgement on the merit of the PR in any way. It is just a way of keeping the PR queue manageable. If you think that is incorrect, or the pull request requires review, you can revive the PR at any time. |
68e2be0 to
0b626a8
Compare
0b626a8 to
803b4f6
Compare
803b4f6 to
015f87b
Compare
015f87b to
1284eb0
Compare
d1c11ad to
5de0a95
Compare
| for (Snapshot snapshot : metadata.snapshots()) { | ||
| String newManifestListLocation = | ||
| newPath(snapshot.manifestListLocation(), sourcePrefix, targetPrefix); | ||
| String manifestList = snapshot.manifestListLocation(); |
There was a problem hiding this comment.
do we also need to handle ReachableFileCleanup and IncrementalFileCleanup?
| TableMetadata metadata, String sourcePrefix, String targetPrefix) { | ||
| List<Snapshot> newSnapshots = Lists.newArrayListWithCapacity(metadata.snapshots().size()); | ||
| for (Snapshot snapshot : metadata.snapshots()) { | ||
| String newManifestListLocation = |
There was a problem hiding this comment.
there are a few more places in this class that reference snapshot.manifestListLocation() directly, should we update those as well?
|
I haven't been looking into this for a long time, but do we still need to make #17545 a dependency of this one? As I see we made the |
gaborkaszab
left a comment
There was a problem hiding this comment.
Sorry I wasn't active on this PR for a while. I think the scope has change in the meantime, got narrowed to simply introducing the new root-manifest field. Left some comments with this in mind.
Also, just a thought on the direct follow-ups: There are a handful of new fields introduced for V4 snapshots. This PR introduces a single one of them. Shouldn't we do them in bulk by a single PR?
| keyId); | ||
| } | ||
|
|
||
| BaseSnapshot( |
There was a problem hiding this comment.
Aren't these constructors asymmetric? We have now a variation that accepts manifest list only, and another one that accepts both manifest list and root manifest. To restore symmetry, we either need a third one accepting root manifest only, or simply having the one that accepts both without the other 2 variations. I would personally go for the second option.
There was a problem hiding this comment.
went with your recommendation and modify the existing constructor directly.
| Iterables.transform(snapshot.allManifests(DUMMY_FILE_IO), ManifestFile::path), | ||
| generator); | ||
| String rootManifest = snapshot.rootManifestLocation(); | ||
| if (rootManifest != null) { |
There was a problem hiding this comment.
This is an "if - else { if - else}" structure. I think we can use a cleaner "if - else if - else" non-nested structure by adding String rootManifest = snapshot.rootManifestLocation() above the if.
There was a problem hiding this comment.
Done. toJson now reads both locations up front and uses a flat if / else if / else.
| String rootManifest = snapshot.rootManifestLocation(); | ||
| if (rootManifest != null) { | ||
| generator.writeStringField(ROOT_MANIFEST, rootManifest); | ||
| } else { |
There was a problem hiding this comment.
I think the order of the checks is off. Previously we did "if not V2 then V1". Shouldn't we check V3 root manifest first, then V2 manifest list, then V1?
There was a problem hiding this comment.
Agreed. The order is now root-manifest first, then manifest-list, then the embedded v1 manifests.
|
|
||
| if (manifestList != null || rootManifest != null) { | ||
| // manifest-list is used by v1-v3 snapshots, root-manifest by v4 snapshots | ||
| return new BaseSnapshot( |
There was a problem hiding this comment.
This seems another reason why we should drop the manifest list only constructor.
There was a problem hiding this comment.
Yes, with the list-only constructor gone, fromJson now has a single call that passes both manifestList and rootManifest. The v1 embedded-manifests constructor is still separate.
| 6, | ||
| "summary", | ||
| Types.MapType.ofRequired(7, 8, Types.StringType.get(), Types.StringType.get()))); | ||
| Types.MapType.ofRequired(7, 8, Types.StringType.get(), Types.StringType.get())), |
There was a problem hiding this comment.
I recall @rdblue is in favor of not implementing metadata table support right away as we might want to re-consider how they should look like in V4.
I know I've commented before not to add coverage for metadata table in this PR, and I still think this way.
There was a problem hiding this comment.
Agreed. I removed the snapshots metadata table changes from this PR. We can decide how v4 should show up in metadata tables in a separate discussion.
| FileFormat.AVRO, fileIO.newInputFile(snapshot.manifestListLocation())) | ||
| FileFormat.AVRO, | ||
| ManifestLists.newInputFile( | ||
| fileIO, new BaseFileWithEncryptedKey(location, null, snapshot.keyId(), null))) |
There was a problem hiding this comment.
I'm not entirely sure about the scope of this PR now. Based on the title I have the impression that we introduce support for the new root-manifest field to Snapshot and its parsers. Here we also do some other stuff:
- Route
InputFilecreation through the cachingManifestListspath. - Introduce the more generic abstraction of
ManifestListFilein order to be able to callManifestLists.newInputFile.
Currently, I don't think that any of the above is the scope of this PR. Then we could remove the dependency on #17545 too.
There was a problem hiding this comment.
Agreed on the scope. I removed the FileCleanupStrategy change and the ManifestLists routing, so this PR no longer depends on #17545. It now only adds the root-manifest field to Snapshot, BaseSnapshot, and SnapshotParser, plus the path rewrite in RewriteTablePathUtil.
When the selected snapshot references a Parquet root manifest, plan the scan through the native FilePlanner instead of the manifest-list ManifestGroup path. DataTableScan and BaseDistributedDataScan gate on root-manifest presence via a shared SnapshotScan.planV4RootManifest helper, so manifest-list snapshots (v1-v3 and those retained across an upgrade) are unaffected. RootManifestFile.from resolves the root against the table location; column stats and manifest-scan metrics are forwarded; unsupported entries (encrypted roots, column files, pre-v4 delete manifests) are rejected when encountered. Stacked on the preceding cherry-picked commit "API, Core: Add v4 root manifest location to Snapshot" (duplicated from apache#17523 to unblock the read path); drop it once apache#17523 merges on its own.
V4 snapshots reference a single root manifest instead of a manifest list (spec change in apache#16025). Add Snapshot.rootManifestLocation() and serialize it as the "root-manifest" field, keeping manifestListLocation() and "manifest-list" for v1 through v3. BaseSnapshot stores the location and does not support reading manifests from a root manifest until the reader lands. Root-manifest snapshots are built through BaseSnapshot.withRootManifest(), which requires an operation, a first-row-id, and a sequence number greater than 0; keeping manifest-list and root-manifest construction on separate paths makes setting both impossible. SnapshotParser rejects a root-manifest snapshot that also sets a manifest list or is missing sequence-number, summary, summary.operation, first-row-id, or added-rows. The v1 through v3 fallback that defaults a missing operation to overwrite is preserved for manifest-list snapshots and skipped for root-manifest snapshots, which require an operation. Producing a root manifest, reading one, and updating the manifest-list-centric callers (expire and cleanup, metadata tables, RewriteTablePath) are deferred to the root manifest reader and writer, which are not yet in the tree. This duplicates the standalone rootManifestLocation change from apache#17523 (currently stacked on encryption PR apache#17545). It is included here only to unblock the v4 read path and should be rebased/dropped once apache#17523 lands on its own. Spec PR: apache#16025 (cherry picked from commit 02eb1329e21d06f8ed8acb5702702a180a0a9051)
Route a data scan to the v4 `FilePlanner` when the selected snapshot references a root manifest. Stacked on a cherry-picked commit from apache#17523
v4 snapshots store their root file in "root-manifest" instead of "manifest-list". Add Snapshot.rootManifestLocation() and serialize it as the "root-manifest" JSON field, keeping manifestListLocation() and "manifest-list" for v1 through v3. Each snapshot sets exactly one of the two. Read-path subset of apache#17523 (commit 5de0a95), excluding its encryption dependency and the manifest-list-centric callers (metadata tables, cleanup, RewriteTablePath) plus the engine and doc changes the read path does not need. (cherry picked from commit 5de0a95)
Route a data scan to the v4 `FilePlanner` when the selected snapshot references a root manifest. Stacked on a cherry-picked commit from apache#17523
v4 snapshot metadata has no format-version field and stores the snapshot's root file in root-manifest instead of manifest-list. Add Snapshot.rootManifestLocation() and keep manifestListLocation() for v1-v3 snapshots. Each snapshot sets at most one of the two. Commits still write a manifest list. Readers that open the snapshot file as a manifest list stay on manifestListLocation() until a root manifest is written. Generated-by: Cursor Grok 4.7 Co-authored-by: Cursor <cursoragent@cursor.com>
| Iterables.transform(snapshot.allManifests(DUMMY_FILE_IO), ManifestFile::path), | ||
| generator); | ||
| String rootManifest = snapshot.rootManifestLocation(); | ||
| if (rootManifest != null) { |
There was a problem hiding this comment.
Done. toJson now reads both locations up front and uses a flat if / else if / else.
| String rootManifest = snapshot.rootManifestLocation(); | ||
| if (rootManifest != null) { | ||
| generator.writeStringField(ROOT_MANIFEST, rootManifest); | ||
| } else { |
There was a problem hiding this comment.
Agreed. The order is now root-manifest first, then manifest-list, then the embedded v1 manifests.
|
|
||
| if (manifestList != null || rootManifest != null) { | ||
| // manifest-list is used by v1-v3 snapshots, root-manifest by v4 snapshots | ||
| return new BaseSnapshot( |
There was a problem hiding this comment.
Yes, with the list-only constructor gone, fromJson now has a single call that passes both manifestList and rootManifest. The v1 embedded-manifests constructor is still separate.
| 6, | ||
| "summary", | ||
| Types.MapType.ofRequired(7, 8, Types.StringType.get(), Types.StringType.get()))); | ||
| Types.MapType.ofRequired(7, 8, Types.StringType.get(), Types.StringType.get())), |
There was a problem hiding this comment.
Agreed. I removed the snapshots metadata table changes from this PR. We can decide how v4 should show up in metadata tables in a separate discussion.
| FileFormat.AVRO, fileIO.newInputFile(snapshot.manifestListLocation())) | ||
| FileFormat.AVRO, | ||
| ManifestLists.newInputFile( | ||
| fileIO, new BaseFileWithEncryptedKey(location, null, snapshot.keyId(), null))) |
There was a problem hiding this comment.
Agreed on the scope. I removed the FileCleanupStrategy change and the ManifestLists routing, so this PR no longer depends on #17545. It now only adds the root-manifest field to Snapshot, BaseSnapshot, and SnapshotParser, plus the path rewrite in RewriteTablePathUtil.
| Map<String, String> summary, | ||
| Integer schemaId, | ||
| String manifestList, | ||
| String rootManifest, |
There was a problem hiding this comment.
I followed @gaborkaszab 's suggestion and dropped the list-only overload, keeping a single constructor that takes both locations. Existing callers pass null for rootManifest.
The upside is a single signature. The downside is every previous list-only call site now passes an extra null, which leads to churning in test code.
This is how BaseSnapshot has grown before: schemaId (#2275), firstRowId/addedRows (#11948), and keyId (#12927) were added to the same constructor.
There was a problem hiding this comment.
We might want to introduce a dedicated contract once all the other V4 Snapshot fields are introduced, but let it be the concert of the follow-ups, not this one.
Thanks!
| keyId); | ||
| } | ||
|
|
||
| BaseSnapshot( |
There was a problem hiding this comment.
went with your recommendation and modify the existing constructor directly.
@gaborkaszab I would separate them out. this can get in first to unblock @anoopj |
| * Return the location of this snapshot's root manifest, or null if this snapshot has no root | ||
| * manifest. | ||
| * | ||
| * <p>v4 snapshots store this in {@code root-manifest} instead of a manifest list. At most one of |
There was a problem hiding this comment.
"At most one of this..." reads a bit confusing. Maybe just say something like "Snapshots in v1-v3 use {@link #manifestListLocation()} instead"?
| null /* summary */, | ||
| 1 /* schemaId */, | ||
| "ml.avro" /* manifestListLocation */, | ||
| null /* rootManifest */, |
There was a problem hiding this comment.
we should maybe have a test in this class where root manifest is set
| snapshot.schemaId(), | ||
| newManifestListLocation, | ||
| manifestList == null ? null : newPath(manifestList, sourcePrefix, targetPrefix), | ||
| rootManifest == null ? null : newPath(rootManifest, sourcePrefix, targetPrefix), |
There was a problem hiding this comment.
we should probably have a test in TestRewriteTablePathUtil that makes sure the root manifest is rewritten to the new location
| @@ -166,10 +166,26 @@ default Iterable<DeleteFile> removedDeleteFiles(FileIO io) { | |||
| /** | |||
| * Return the location of this snapshot's manifest list, or null if it is not separate. | |||
There was a problem hiding this comment.
The "or null if.." part is also relevant now here, right?
| * Return the location of this snapshot's manifest list, or null if it is not separate. | ||
| * | ||
| * @return the location of the manifest list for this Snapshot | ||
| * <p>Only set for tables with format version 1 through 3. Snapshots in v4 tables use {@link |
There was a problem hiding this comment.
I'm hesitant adding such a comment here. We also don't mention that for first-row-id, added-rows etc. are only relevant from V3 onwards.
I'd like it to the spec to describe the relation between this and rootManifestLocation.
| * Return the location of this snapshot's root manifest, or null if this snapshot has no root | ||
| * manifest. | ||
| * | ||
| * <p>v4 snapshots store this in {@code root-manifest} instead of a manifest list. At most one of |
| Map<String, String> summary, | ||
| Integer schemaId, | ||
| String manifestList, | ||
| String rootManifest, |
There was a problem hiding this comment.
We might want to introduce a dedicated contract once all the other V4 Snapshot fields are introduced, but let it be the concert of the follow-ups, not this one.
Thanks!
| Long firstRowId, | ||
| Long addedRows, | ||
| String keyId) { | ||
| Preconditions.checkArgument( |
There was a problem hiding this comment.
This verifies they can't be set together. Should we also verify that one of them has to be set?
| if (allManifests == null) { | ||
| // if manifests isn't set, then the snapshotFile is set and should be read to get the list | ||
| // if manifests isn't set, then the manifest list must be read | ||
| Preconditions.checkArgument( |
There was a problem hiding this comment.
Don't we want to reverse this and at L181 verify that rootManifestLocation is null, otherwise throw that we don't support this for V4. We could leave the rest of the function intact.
| String rootManifest = JsonUtil.getStringOrNull(ROOT_MANIFEST, node); | ||
|
|
||
| if (manifestList != null || rootManifest != null) { | ||
| // manifest-list is used by v1-v3 snapshots, root-manifest by v4 snapshots |
There was a problem hiding this comment.
nit: comment is not needed
| for (Snapshot snapshot : metadata.snapshots()) { | ||
| String newManifestListLocation = | ||
| newPath(snapshot.manifestListLocation(), sourcePrefix, targetPrefix); | ||
| String manifestList = snapshot.manifestListLocation(); |
There was a problem hiding this comment.
We're introducing the new field to Snapshot, I don't think that we should implement table rewrite functionality right away. Could that be separate?
| public void testAddSnapshotWithRootManifest() { | ||
| String action = MetadataUpdateParser.ADD_SNAPSHOT; | ||
| long snapshotId = 2; | ||
| int schemaId = 3; |
There was a problem hiding this comment.
nit: this could be inline at L443
|
|
||
| assertThat(snapshot.snapshotId()).isEqualTo(expected.snapshotId()); | ||
| assertThat(snapshot.manifestListLocation()).isEqualTo(expected.manifestListLocation()); | ||
| assertThat(snapshot.rootManifestLocation()).isEqualTo(expected.rootManifestLocation()); |
There was a problem hiding this comment.
Not sure what coverage we add with these asserts on rootManifestLocation, because they are always null, except in one dedicated test. Should we assert isNull() as we do with other fields below?
v4 snapshots store a root-manifest instead of a manifest-list, so Snapshot needs to expose that location. This adds Snapshot.rootManifestLocation() defaulting to null, parsed and written as root-manifest. manifestListLocation is unchanged for v1-v3, and at most one of the two is set. Commits still write a manifest list and readers stay on manifestListLocation until a root manifest write path exists. The snapshots metadata table column and the encryption abstraction were removed from this PR.
Test plan: TestSnapshotJson and TestMetadataUpdateParser round-trips for root-manifest.
AI assisted.