Skip to content

feat: introduce exact file format identity and persisted codecs#7879

Open
Xuanwo wants to merge 2 commits into
mainfrom
xuanwo/exact-file-format-identity
Open

feat: introduce exact file format identity and persisted codecs#7879
Xuanwo wants to merge 2 commits into
mainfrom
xuanwo/exact-file-format-identity

Conversation

@Xuanwo

@Xuanwo Xuanwo commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Part of #7877.

LanceFileVersion currently combines user-facing selectors (Stable and Next) with exact identities that can be persisted. It also exposes one numeric conversion for wire locations whose compatibility mappings are deliberately different.

This first independently mergeable step introduces LanceFileFormat as the exact, unordered file-format identity while leaving LanceFileVersion in lance-encoding. Selectors resolve before persisted metadata is constructed, and manifest strings, DataFile fields, standard footers, and embedded footers use location-specific codecs.

Existing wire mappings, legacy empty-manifest recovery, reader compatibility, and mixed-version rejection remain unchanged. Version-specific dispatch and encoding/compression/dataset execution migration are intentionally deferred to later PRs in the stack.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Pro Plus

Run ID: 56942ffe-3ed8-4b3e-9e21-7f0385ad4813

📥 Commits

Reviewing files that changed from the base of the PR and between cbb3377 and 543fe0d.

📒 Files selected for processing (1)
  • python/src/dataset.rs

📝 Walkthrough

Walkthrough

This change introduces exact LanceFileFormat identities, adds codecs for manifest, data-file, and footer representations, and migrates Lance file creation, reading, manifest handling, transactions, optimization, and tests to use the exact format type.

Changes

Exact format migration

Layer / File(s) Summary
Format type and codecs
rust/lance-file/src/version.rs, rust/lance-file/src/lib.rs
Adds exact format identities with canonical manifest, data-file, footer, legacy compatibility, display, and conversion support.
Footer read and write mapping
rust/lance-file/src/reader.rs, rust/lance-file/src/writer.rs
Uses exact format mappings for standard and embedded footer numbers, with round-trip assertions.
Table metadata storage
rust/lance-table/src/format/*, rust/lance-table/src/io/*
Stores and infers exact formats in DataFile, Fragment, DataStorageFormat, and Manifest, including canonical protobuf encoding.
Dataset integration and validation
rust/lance/src/dataset/*, rust/lance/src/io/*, python/src/dataset.rs
Updates dataset reads, writes, transactions, optimization, cleanup, conflict handling, tests, and Python version formatting to use LanceFileFormat.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

  • lance-format/lance#7877 — Covers the exact LanceFileFormat identity and persisted codec behavior implemented here.

Suggested labels: A-format

Suggested reviewers: jackye1995

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: introducing exact file format identity and persisted codecs.
Description check ✅ Passed The description is directly aligned with the changeset and accurately describes the new exact file-format identity work.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch xuanwo/exact-file-format-identity

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added A-encoding Encoding, IO, file reader/writer enhancement New feature or request labels Jul 21, 2026
@Xuanwo
Xuanwo marked this pull request as ready for review July 21, 2026 08:51

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rust/lance/src/dataset/transaction.rs (1)

2299-2308: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

.expect() panics on a fallible version conversion in build_manifest.

new_file.file_version() returns Result<LanceFileFormat> and errors on an unrecognized (major, minor) combination. Calling .expect(...) here turns a malformed DataFile in a DataReplacement transaction into a panic instead of a descriptive commit error. build_manifest already returns Result, so this should propagate via ?.

As per coding guidelines: "Never use .unwrap(), .expect(), panic!(), or assert!() in library code for fallible operations; use ? with Result and proper error types."

🛡️ Proposed fix
                     if columns_covered.is_disjoint(&new_file.fields.iter().collect()) {
-                        new_file
-                            .file_version()
-                            .expect("Expected valid file version");
+                        new_file.file_version()?;
                         new_frag.files.push(new_file.clone());
                     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance/src/dataset/transaction.rs` around lines 2299 - 2308, In
build_manifest, replace the fallible file_version() .expect call in the
columns_covered disjoint branch with ? so invalid DataFile version combinations
return the existing commit error. Preserve the subsequent
new_frag.files.push(new_file.clone()) behavior for valid versions.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rust/lance-file/src/writer.rs`:
- Around line 870-877: Change standard_footer_numbers to return a Result,
replacing the unsupported-version panic with the appropriate error. Update
finish to propagate that error with ?, preserving normal footer-number
conversion for supported versions.

In `@rust/lance/src/dataset/optimize/binary_copy.rs`:
- Around line 199-204: Update rewrite_files_binary_copy’s
LanceFileFormat::from_data_file_numbers conversion to propagate conversion
failures with ? instead of unwrap(). Preserve the existing conversion to
LanceFileVersion and the function’s Result<Vec<Fragment>> error flow.

---

Outside diff comments:
In `@rust/lance/src/dataset/transaction.rs`:
- Around line 2299-2308: In build_manifest, replace the fallible file_version()
.expect call in the columns_covered disjoint branch with ? so invalid DataFile
version combinations return the existing commit error. Preserve the subsequent
new_frag.files.push(new_file.clone()) behavior for valid versions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Pro Plus

Run ID: 5ba2e859-e7fb-4b2f-99cd-d33d8f0854e6

📥 Commits

Reviewing files that changed from the base of the PR and between a3c6fce and cbb3377.

📒 Files selected for processing (23)
  • rust/lance-file/src/lib.rs
  • rust/lance-file/src/reader.rs
  • rust/lance-file/src/version.rs
  • rust/lance-file/src/writer.rs
  • rust/lance-table/src/format/fragment.rs
  • rust/lance-table/src/format/manifest.rs
  • rust/lance-table/src/io/commit.rs
  • rust/lance-table/src/io/manifest.rs
  • rust/lance/src/dataset.rs
  • rust/lance/src/dataset/fragment.rs
  • rust/lance/src/dataset/fragment/write.rs
  • rust/lance/src/dataset/optimize.rs
  • rust/lance/src/dataset/optimize/binary_copy.rs
  • rust/lance/src/dataset/optimize/tests/binary_copy.rs
  • rust/lance/src/dataset/schema_evolution.rs
  • rust/lance/src/dataset/tests/dataset_io.rs
  • rust/lance/src/dataset/tests/dataset_merge_update.rs
  • rust/lance/src/dataset/transaction.rs
  • rust/lance/src/dataset/updater.rs
  • rust/lance/src/dataset/write.rs
  • rust/lance/src/dataset/write/commit.rs
  • rust/lance/src/io/commit.rs
  • rust/lance/src/io/commit/conflict_resolver.rs

Comment on lines +870 to 877
fn standard_footer_numbers(&self) -> (u16, u16) {
let version = self.version();
let exact_version = LanceFileFormat::from(version);
if exact_version == crate::version::LanceFileFormat::V1 {
panic!("Unsupported version: {}", version);
}
exact_version.to_standard_footer_numbers()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Replace panic! with a returned error on the fallible finish() path.

version() derives from the public FileWriterOptions.format_version, so a caller passing Some(LanceFileVersion::Legacy) reaches this panic! through finish() (which returns Result). Unsupported versions should surface as an error, not a panic.

🛡️ Return an error instead of panicking
-    fn standard_footer_numbers(&self) -> (u16, u16) {
-        let version = self.version();
-        let exact_version = LanceFileFormat::from(version);
-        if exact_version == crate::version::LanceFileFormat::V1 {
-            panic!("Unsupported version: {}", version);
-        }
-        exact_version.to_standard_footer_numbers()
-    }
+    fn standard_footer_numbers(&self) -> Result<(u16, u16)> {
+        let version = self.version();
+        let exact_version = LanceFileFormat::from(version);
+        if exact_version == LanceFileFormat::V1 {
+            return Err(Error::NotSupported {
+                source: format!(
+                    "the v2 file writer cannot write legacy (v1) files; requested version was {}",
+                    version
+                )
+                .into(),
+                location: location!(),
+            });
+        }
+        Ok(exact_version.to_standard_footer_numbers())
+    }

And update the call site in finish():

-        let (major, minor) = self.standard_footer_numbers();
+        let (major, minor) = self.standard_footer_numbers()?;
As per coding guidelines: "Never use `.unwrap()`, `.expect()`, `panic!()`, or `assert!()` in library code for fallible operations; use `?` with `Result` and proper error types."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn standard_footer_numbers(&self) -> (u16, u16) {
let version = self.version();
let exact_version = LanceFileFormat::from(version);
if exact_version == crate::version::LanceFileFormat::V1 {
panic!("Unsupported version: {}", version);
}
exact_version.to_standard_footer_numbers()
}
fn standard_footer_numbers(&self) -> Result<(u16, u16)> {
let version = self.version();
let exact_version = LanceFileFormat::from(version);
if exact_version == LanceFileFormat::V1 {
return Err(Error::NotSupported {
source: format!(
"the v2 file writer cannot write legacy (v1) files; requested version was {}",
version
)
.into(),
location: location!(),
});
}
Ok(exact_version.to_standard_footer_numbers())
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance-file/src/writer.rs` around lines 870 - 877, Change
standard_footer_numbers to return a Result, replacing the unsupported-version
panic with the appropriate error. Update finish to propagate that error with ?,
preserving normal footer-number conversion for supported versions.

Source: Coding guidelines

Comment on lines +199 to +204
let version: LanceFileVersion = LanceFileFormat::from_data_file_numbers(
fragments[0].files[0].file_major_version,
fragments[0].files[0].file_minor_version,
)
.unwrap()
.resolve();
.into();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

.unwrap() panics on an unrecognized persisted file version in rewrite_files_binary_copy.

LanceFileFormat::from_data_file_numbers errors on any unrecognized (major, minor) combination read from fragments[0].files[0]. Panicking here means a corrupt or unexpectedly-versioned data file crashes a compaction task instead of failing with a descriptive error. The function already returns Result<Vec<Fragment>>, so this should propagate via ? — matching the non-panicking is_ok_and pattern used for the same conversion in can_use_binary_copy_impl (rust/lance/src/dataset/optimize.rs) in this same PR.

As per coding guidelines: "Never use .unwrap(), .expect(), panic!(), or assert!() in library code for fallible operations; use ? with Result and proper error types."

🛡️ Proposed fix
-    let version: LanceFileVersion = LanceFileFormat::from_data_file_numbers(
+    let version: LanceFileVersion = LanceFileFormat::from_data_file_numbers(
         fragments[0].files[0].file_major_version,
         fragments[0].files[0].file_minor_version,
-    )
-    .unwrap()
-    .into();
+    )?
+    .into();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let version: LanceFileVersion = LanceFileFormat::from_data_file_numbers(
fragments[0].files[0].file_major_version,
fragments[0].files[0].file_minor_version,
)
.unwrap()
.resolve();
.into();
let version: LanceFileVersion = LanceFileFormat::from_data_file_numbers(
fragments[0].files[0].file_major_version,
fragments[0].files[0].file_minor_version,
)?
.into();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance/src/dataset/optimize/binary_copy.rs` around lines 199 - 204,
Update rewrite_files_binary_copy’s LanceFileFormat::from_data_file_numbers
conversion to propagate conversion failures with ? instead of unwrap(). Preserve
the existing conversion to LanceFileVersion and the function’s
Result<Vec<Fragment>> error flow.

Source: Coding guidelines

@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

@github-actions github-actions Bot added the A-python Python bindings label Jul 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-encoding Encoding, IO, file reader/writer A-python Python bindings enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant