Skip to content

feat(tools): add table manifest command#7970

Open
bugwz wants to merge 1 commit into
lance-format:mainfrom
bugwz:lance-tools-for-manifest
Open

feat(tools): add table manifest command#7970
bugwz wants to merge 1 commit into
lance-format:mainfrom
bugwz:lance-tools-for-manifest

Conversation

@bugwz

@bugwz bugwz commented Jul 24, 2026

Copy link
Copy Markdown

feat(tools): add table manifest command

Which issue does this PR close?

None.

What changes are included in this PR?

This PR adds a lance-tools table manifest command for inspecting Lance table manifest files from local or object-store paths.

Included changes:

  • Add the table manifest --source <manifest> CLI entry point to lance-tools.
  • Render manifest-level details including version, branch, writer version, timestamp, feature flags, row id state, schema, fragments, base paths, config, table metadata, index metadata, and transaction metadata.
  • Load optional manifest sections such as index and transaction sections when present, while reporting section read errors in the rendered output.
  • Expose read_manifest_proto from lance-table so tools can read the raw protobuf manifest before conversion to the semantic Manifest type.
  • Document the new lance-tools table manifest command in the tool README.
  • Add test coverage that parses checked-in test_data manifests and verifies the rendered output contains the expected top-level sections.

Are there any user-facing changes?

Yes. Users can now inspect a table manifest with:

lance-tools table manifest --source path/to/table/_versions/1.manifest

This is an additive CLI feature and does not change existing commands or public dataset behavior.

Testing

  • git diff --cached --check

Targeted Rust tests were not run locally for this temporary PR description.

@github-actions github-actions Bot added A-deps Dependency updates enhancement New feature or request labels Jul 24, 2026
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds raw manifest protobuf reading and separates semantic conversion. Extends lance-tools with a table manifest command that loads manifest sections and renders metadata, fragments, indices, transactions, and related details.

Changes

Manifest inspection

Layer / File(s) Summary
Manifest protobuf reader
rust/lance-table/src/io/manifest.rs
Separates byte retrieval and validation from protobuf decoding, exposes read_manifest_proto, and converts decoded protobufs through Manifest::try_from.
Manifest source loading
rust/lance-tools/src/manifest.rs
Infers dataset bases, loads manifests from object storage, and reads inline or external index and transaction sections.
Manifest report rendering
rust/lance-tools/src/manifest.rs
Renders manifest fields, summaries, schema, fragments, files, indices, transactions, maps, timestamps, and feature flags.
CLI integration and validation
rust/lance-tools/src/cli.rs, rust/lance-tools/src/lib.rs, rust/lance-tools/Cargo.toml, rust/lance-tools/README.md, rust/lance-tools/src/manifest.rs
Adds table manifest --source, wires command dispatch, adds dependencies and documentation, and tests manifest loading and rendering across repository test data.

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

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant LanceToolsArgs
  participant LanceToolManifest
  participant ObjectStore
  participant Writer
  User->>LanceToolsArgs: run table manifest --source
  LanceToolsArgs->>LanceToolManifest: show_table_manifest
  LanceToolManifest->>ObjectStore: read manifest and sections
  ObjectStore-->>LanceToolManifest: return manifest data
  LanceToolManifest->>Writer: write formatted report
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a table manifest command to lance-tools.
Description check ✅ Passed The description matches the changeset and clearly explains the new manifest command, docs, and tests.
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 unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@bugwz
bugwz force-pushed the lance-tools-for-manifest branch from b50c8d0 to a20868d Compare July 24, 2026 10:32

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
rust/lance-tools/src/manifest.rs (1)

34-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Tighten LanceToolManifest visibility to pub(crate).

LanceToolManifest is only used internally (via the pub(crate) show_table_manifest and tests); its fields are private and it is not part of an intended public API. Since lib.rs re-exports this module (pub mod manifest;), leaving it pub leaks an internal type onto the crate's public surface.

As per coding guidelines: "Prefer pub(crate) over pub for crate-internal items, and use pub use re-exports for the actual public API surface."

♻️ Proposed change
-pub struct LanceToolManifest {
+pub(crate) struct LanceToolManifest {
🤖 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-tools/src/manifest.rs` around lines 34 - 41, Change the visibility
of the LanceToolManifest struct from pub to pub(crate), keeping its fields and
implementation unchanged so the internally used type is no longer exposed as
part of the public API.

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.

Nitpick comments:
In `@rust/lance-tools/src/manifest.rs`:
- Around line 34-41: Change the visibility of the LanceToolManifest struct from
pub to pub(crate), keeping its fields and implementation unchanged so the
internally used type is no longer exposed as part of the public API.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Pro Plus

Run ID: d8f30995-630a-4605-9599-87d5700ab8d4

📥 Commits

Reviewing files that changed from the base of the PR and between b50c8d0 and a20868d.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • rust/lance-table/src/io/manifest.rs
  • rust/lance-tools/Cargo.toml
  • rust/lance-tools/README.md
  • rust/lance-tools/src/cli.rs
  • rust/lance-tools/src/lib.rs
  • rust/lance-tools/src/manifest.rs

@bugwz
bugwz force-pushed the lance-tools-for-manifest branch from a20868d to fecb66a Compare July 25, 2026 04:38

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (2)
rust/lance-tools/src/manifest.rs-608-618 (1)

608-618: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

uuid_bytes prints a length, not bytes.

In the parse-failure branch — the one path an operator uses to diagnose a corrupt index entry — this emits the byte count (e.g. 16), which conveys nothing about the malformed uuid. Rename the key to reflect a count, and consider hex-dumping the raw bytes instead.

♻️ Proposed change
             write_kv(
                 f,
                 8,
-                "uuid_bytes",
+                "uuid_byte_len",
                 &index
                     .raw
                     .uuid
                     .as_ref()
                     .map(|uuid| uuid.uuid.len().to_string())
                     .unwrap_or_else(|| "absent".to_string()),
             )?;

As per coding guidelines: "for numeric parameters state whether the value is an id, count, index, etc." and "Use precise domain terminology."

🤖 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-tools/src/manifest.rs` around lines 608 - 618, Update the write_kv
call in the parse-failure manifest formatting path to rename the uuid_bytes key
to a precise byte-count name such as uuid_byte_count, matching the existing
length value; if retaining diagnostic detail, add a separate hex representation
of the raw UUID bytes rather than labeling the count as bytes.

Source: Coding guidelines

rust/lance-tools/src/manifest.rs-855-855 (1)

855-855: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Render update_mode as the enum variant name.

update.update_mode is a prost open enum field (i32), so .to_string() writes 0/1, which obscures the update mode in manifest dumps. Use the generated accessor/enumerator and only fall back to the numeric value for unknown discriminators.

🤖 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-tools/src/manifest.rs` at line 855, Update the manifest rendering
in the write_kv call for update.update_mode to resolve the prost enum
discriminator through its generated accessor or enumerator, emitting the enum
variant name for known values and retaining the numeric discriminator only for
unknown values.

Source: Coding guidelines

🧹 Nitpick comments (5)
rust/lance-tools/src/manifest.rs (5)

111-133: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the recognized manifest layouts and the None case.

The inference rules (<base>/_latest.manifest and <base>/_versions/*.manifest) and the meaning of None (base not inferable → external transaction files cannot be resolved, per Lines 173-178) are non-obvious to a reader. Also worth noting that a root-level manifest yields None because path_from_parts rejects an empty prefix.

📝 Proposed doc comments
+/// Infers the dataset base directory from a manifest path.
+///
+/// Recognizes two layouts: `<base>/_latest.manifest` and `<base>/_versions/<n>.manifest`.
+/// Returns `None` when the path matches neither layout, or when the inferred base would be
+/// empty (a manifest sitting at the store root). Callers must then report external
+/// transaction files as unresolvable rather than guessing a base.
 fn infer_dataset_base_from_manifest_path(path: &Path) -> Option<Path> {

As per coding guidelines: "Add doc comments to magic constants, thresholds, and non-obvious transformation functions" and "Comment fallback or guard code paths with when they trigger and why they exist."

🤖 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-tools/src/manifest.rs` around lines 111 - 133, Add doc comments
for infer_dataset_base_from_manifest_path and path_from_parts documenting the
recognized <base>/_latest.manifest and <base>/_versions/*.manifest layouts, that
None means the dataset base cannot be inferred and external transaction files
cannot be resolved, and that root-level manifests return None because
path_from_parts rejects an empty prefix.

Source: Coding guidelines


73-97: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid cloning the entire manifest protobuf for two presence checks.

raw_manifest is retained solely to test timestamp.is_some() (Line 239) and data_format.is_some() (Line 249). The clone at Line 75 duplicates the whole proto — schema fields plus every fragment — doubling peak memory on large tables. Capture the two booleans instead and drop the field.

♻️ Proposed refactor
-        let raw_manifest =
-            read_manifest_proto(&source.object_store, &source.manifest_path, None).await?;
-        let manifest = Manifest::try_from(raw_manifest.clone())?;
+        let raw_manifest =
+            read_manifest_proto(&source.object_store, &source.manifest_path, None).await?;
+        let has_timestamp = raw_manifest.timestamp.is_some();
+        let has_data_format = raw_manifest.data_format.is_some();
+        let manifest = Manifest::try_from(raw_manifest)?;

Then replace the raw_manifest: pb::Manifest field with has_timestamp: bool / has_data_format: bool and update Lines 239 and 249.

🤖 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-tools/src/manifest.rs` around lines 73 - 97, In the manifest
construction flow, remove the full protobuf clone and replace retained
raw_manifest presence checks with boolean flags captured from the original raw
manifest before conversion. Add has_timestamp and has_data_format fields to the
containing struct, initialize them in this constructor, and update the
timestamp/data-format checks in the relevant methods to use those flags.

1269-1285: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the None inference case and the error-rendering branches.

Both existing unit tests cover only successful inference. Uncovered: paths that match neither layout (a bare foo.manifest, a *.manifest not under _versions, a root-level _latest.manifest), and the indices_error: / transaction_error: output branches at Lines 548 and 683, which no test reaches.

💚 Proposed additional test
#[test]
fn test_infer_dataset_base_returns_none_for_unrecognized_paths() {
    for path in [
        "foo.manifest",
        "dataset/other/1.manifest",
        "dataset/_versions/1.txt",
        "_latest.manifest",
    ] {
        assert!(
            infer_dataset_base_from_manifest_path(&Path::from(path)).is_none(),
            "expected no dataset base for {path}"
        );
    }
}
🤖 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-tools/src/manifest.rs` around lines 1269 - 1285, Add a unit test
alongside test_infer_dataset_base_from_version_manifest_path and
test_infer_dataset_base_from_latest_manifest_path covering unrecognized paths,
including bare, wrong-directory, wrong-extension, and root-level latest
manifests, and assert infer_dataset_base_from_manifest_path returns None. Also
add test coverage that exercises the indices_error and transaction_error
rendering branches at their respective error-formatting sites.

179-195: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

External transaction failures lose the attempted path.

Lines 190-194 surface bare object_store/prost messages, so a missing or corrupt transaction file renders as e.g. NotFound with no indication of which _transactions/<file> was tried. Include path in each message; extracting a small helper also flattens the four-level match pyramid.

♻️ Proposed refactor
-        match object_store.inner.get(&path).await {
-            Ok(result) => match result.bytes().await {
-                Ok(data) => match pb::Transaction::decode(data) {
-                    Ok(transaction) => OptionalSection::Loaded(TransactionView {
-                        source: TransactionSource::External { path },
-                        transaction,
-                    }),
-                    Err(e) => OptionalSection::Error(e.to_string()),
-                },
-                Err(e) => OptionalSection::Error(e.to_string()),
-            },
-            Err(e) => OptionalSection::Error(e.to_string()),
-        }
+        match read_external_transaction(object_store, &path).await {
+            Ok(transaction) => OptionalSection::Loaded(TransactionView {
+                source: TransactionSource::External { path },
+                transaction,
+            }),
+            Err(e) => OptionalSection::Error(format!(
+                "failed to read transaction file {}: {}",
+                path, e
+            )),
+        }

with:

async fn read_external_transaction(
    object_store: &ObjectStore,
    path: &Path,
) -> Result<pb::Transaction> {
    let data = object_store.inner.get(path).await?.bytes().await?;
    Ok(pb::Transaction::decode(data)?)
}

As per coding guidelines: "Include full error context, including variable names, values, sizes, and types."

🤖 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-tools/src/manifest.rs` around lines 179 - 195, Update the external
transaction loading flow around TransactionSource::External to include the
attempted path in every object-store, byte-reading, and protobuf decode error
message. Extract the nested reads and decode into a small
read_external_transaction helper if appropriate, then map its errors at the
caller while preserving OptionalSection::Loaded, OptionalSection::Error, and the
existing path value.

Source: Coding guidelines


1183-1187: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document the FLAG_UNKNOWN mask invariant.

FLAG_UNKNOWN - 1 is only the mask of known feature flags because FLAG_UNKNOWN is defined as the first bit after all known flags. Add a comment near this calculation so future flag additions preserve the invariant.

📝 Proposed change
-    let known_mask = FLAG_UNKNOWN - 1;
-    let unknown = flags & !known_mask;
+    // FLAG_UNKNOWN is the first bit above all defined feature flags, so FLAG_UNKNOWN - 1
+    // masks exactly the known bits. Any bit at or above it is reported as unknown.
+    let known_mask = FLAG_UNKNOWN - 1;
+    let unknown = flags & !known_mask;
🤖 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-tools/src/manifest.rs` around lines 1183 - 1187, Add a concise
comment immediately above the known_mask calculation in the manifest
flag-processing code, documenting that FLAG_UNKNOWN is the first bit after all
known feature flags and that FLAG_UNKNOWN - 1 therefore masks known flags.
Ensure future flag additions preserve this ordering invariant.

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-table/src/io/manifest.rs`:
- Around line 30-33: Add synchronized, compiling Rustdoc examples to the public
readers read_manifest_proto (rust/lance-table/src/io/manifest.rs:30-33) and
read_manifest (rust/lance-table/src/io/manifest.rs:120-122), using their current
signatures and documenting links to the relevant protobuf and semantic Manifest
types or methods. Ensure both examples remain valid if the APIs change.
- Around line 35-41: Add direct tests for read_manifest_proto covering normal
round-trip decoding to the expected pb::Manifest and a stale known_size value
that successfully retries and returns the correct protobuf. Extend or add
focused manifest tests alongside test_roundtrip_manifest, reusing existing
object-store and manifest setup.

In `@rust/lance-tools/src/lib.rs`:
- Line 5: Change the manifest module declaration to crate visibility by updating
pub mod manifest to pub(crate) mod manifest; in the crate root. Preserve
internal access from the CLI entrypoint through
crate::manifest::show_table_manifest without exposing lance_tools::manifest
publicly.

In `@rust/lance-tools/src/manifest.rs`:
- Around line 1287-1338: Strengthen test_parse_all_test_data_manifests so it
validates rendered manifest values rather than only the manifest:, schema:, and
fragments: headings. For at least one known checked-in manifest, assert a stable
value such as the version, fragment count, or data-file path, or compare the
rendered output against an approved snapshot while preserving the existing
compatibility checks.
- Around line 34-41: Change the visibility of the crate-internal
LanceToolManifest struct from pub to pub(crate). Keep show_table_manifest as the
crate entry point and leave the struct’s fields and behavior unchanged.
- Around line 1105-1115: Rename format_option_usize and format_optional_usize to
explicitly identify their None sentinels, such as unknown- and absent-specific
names, then update callers at the noted locations so unknown values use the
“unknown” formatter and absent values use the “absent” formatter.

---

Other comments:
In `@rust/lance-tools/src/manifest.rs`:
- Around line 608-618: Update the write_kv call in the parse-failure manifest
formatting path to rename the uuid_bytes key to a precise byte-count name such
as uuid_byte_count, matching the existing length value; if retaining diagnostic
detail, add a separate hex representation of the raw UUID bytes rather than
labeling the count as bytes.
- Line 855: Update the manifest rendering in the write_kv call for
update.update_mode to resolve the prost enum discriminator through its generated
accessor or enumerator, emitting the enum variant name for known values and
retaining the numeric discriminator only for unknown values.

---

Nitpick comments:
In `@rust/lance-tools/src/manifest.rs`:
- Around line 111-133: Add doc comments for
infer_dataset_base_from_manifest_path and path_from_parts documenting the
recognized <base>/_latest.manifest and <base>/_versions/*.manifest layouts, that
None means the dataset base cannot be inferred and external transaction files
cannot be resolved, and that root-level manifests return None because
path_from_parts rejects an empty prefix.
- Around line 73-97: In the manifest construction flow, remove the full protobuf
clone and replace retained raw_manifest presence checks with boolean flags
captured from the original raw manifest before conversion. Add has_timestamp and
has_data_format fields to the containing struct, initialize them in this
constructor, and update the timestamp/data-format checks in the relevant methods
to use those flags.
- Around line 1269-1285: Add a unit test alongside
test_infer_dataset_base_from_version_manifest_path and
test_infer_dataset_base_from_latest_manifest_path covering unrecognized paths,
including bare, wrong-directory, wrong-extension, and root-level latest
manifests, and assert infer_dataset_base_from_manifest_path returns None. Also
add test coverage that exercises the indices_error and transaction_error
rendering branches at their respective error-formatting sites.
- Around line 179-195: Update the external transaction loading flow around
TransactionSource::External to include the attempted path in every object-store,
byte-reading, and protobuf decode error message. Extract the nested reads and
decode into a small read_external_transaction helper if appropriate, then map
its errors at the caller while preserving OptionalSection::Loaded,
OptionalSection::Error, and the existing path value.
- Around line 1183-1187: Add a concise comment immediately above the known_mask
calculation in the manifest flag-processing code, documenting that FLAG_UNKNOWN
is the first bit after all known feature flags and that FLAG_UNKNOWN - 1
therefore masks known flags. Ensure future flag additions preserve this ordering
invariant.
🪄 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: 8e392696-b046-4c02-aefc-2dea9f9573bb

📥 Commits

Reviewing files that changed from the base of the PR and between a20868d and fecb66a.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • rust/lance-table/src/io/manifest.rs
  • rust/lance-tools/Cargo.toml
  • rust/lance-tools/README.md
  • rust/lance-tools/src/cli.rs
  • rust/lance-tools/src/lib.rs
  • rust/lance-tools/src/manifest.rs

Comment on lines +30 to +33
/// Read the raw Manifest protobuf from a URI.
///
/// This only reads manifest files. It does not read data files.
/// This only reads manifest files. It does not read data files or translate the
/// protobuf into the semantic [`Manifest`] type.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Document both public manifest readers with synchronized examples.

  • rust/lance-table/src/io/manifest.rs#L30-L33: add a compiling example for read_manifest_proto and link its protobuf/semantic output types.
  • rust/lance-table/src/io/manifest.rs#L120-L122: add a compiling example for read_manifest and link relevant types or methods.

As per coding guidelines, “Document all public APIs with examples and links to relevant structs and methods; keep examples synchronized with actual signatures.”

📍 Affects 1 file
  • rust/lance-table/src/io/manifest.rs#L30-L33 (this comment)
  • rust/lance-table/src/io/manifest.rs#L120-L122
🤖 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-table/src/io/manifest.rs` around lines 30 - 33, Add synchronized,
compiling Rustdoc examples to the public readers read_manifest_proto
(rust/lance-table/src/io/manifest.rs:30-33) and read_manifest
(rust/lance-table/src/io/manifest.rs:120-122), using their current signatures
and documenting links to the relevant protobuf and semantic Manifest types or
methods. Ensure both examples remain valid if the APIs change.

Source: Coding guidelines

Comment on lines +35 to +41
pub async fn read_manifest_proto(
object_store: &ObjectStore,
path: &Path,
known_size: Option<u64>,
) -> Result<Manifest> {
) -> Result<pb::Manifest> {
let buf = read_manifest_bytes(object_store, path, known_size).await?;
Ok(pb::Manifest::decode(buf)?)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add direct coverage for the new raw-protobuf API.

test_roundtrip_manifest only verifies read_manifest; add tests that assert read_manifest_proto returns the expected protobuf and that a stale known_size retries successfully.

As per coding guidelines, “Every bugfix and feature must have corresponding tests.”

🤖 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-table/src/io/manifest.rs` around lines 35 - 41, Add direct tests
for read_manifest_proto covering normal round-trip decoding to the expected
pb::Manifest and a stale known_size value that successfully retries and returns
the correct protobuf. Extend or add focused manifest tests alongside
test_roundtrip_manifest, reusing existing object-store and manifest setup.

Source: Coding guidelines

// SPDX-FileCopyrightText: Copyright The Lance Authors

pub mod cli;
pub mod manifest;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
git ls-files | rg '(^|/)lib\.rs$|(^|/)cli\.rs$|(^|/)manifest\.rs$|Cargo\.toml$'

echo
echo "lib.rs:"
sed -n '1,80p' rust/lance-tools/src/lib.rs

echo
echo "manifest.rs outline:"
ast-grep outline rust/lance-tools/src/manifest.rs --view compact || true

echo
echo "manifest.rs relevant entries:"
rg -n "^(pub )?|show_table_manifest|TableManifest|struct |enum " rust/lance-tools/src/manifest.rs

echo
echo "cli.rs manifest usages:"
rg -n "crate::manifest|manifest::|show_table_manifest|lance_tools::manifest" rust/lance-tools/src/cli.rs

echo
echo "workspace/crate config relevant:"
sed -n '1,160p' rust/lance-tools/Cargo.toml

Repository: lance-format/lance

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "lance-tools package metadata:"
python3 - <<'PY'
import configparser, re, json
p='rust/lance-tools/Cargo.toml'
text=open(p).read()
print(text)
PY

echo
echo "manifest struct visibility and internal call path:"
rg -n -C 3 '^(pub )?(struct LanceToolManifest|enum OptionalSection|struct IndexView|struct TransactionView|struct TransactionSource|struct ManifestSource|async fn open|impl LanceToolManifest|pub\(crate\) async fn show_table_manifest)' rust/lance-tools/src/manifest.rs
rg -n -C 2 'LanceTableManifestArgs|show_table_manifest|crate::manifest::manifest_path|manifest::show_table_manifest' rust/lance-tools/src/cli.rs rust/lance-tools/src/*.rs

echo
echo "all external references to lance_tools::manifest / crate::manifest outside lance-tools:"
rg -n 'lance_tools::manifest|lance-tools::manifest|lance_tools::util|crate::manifest|manifest::show_table_manifest' --glob '!rust/lance-tools/**' .

echo
echo "all references to public types in manifest.rs (excluding tests/internal helpers if apparent):"
rg -n 'LanceToolManifest|OptionalSection|IndexView|TransactionView|TransactionSource|ManifestSource' rust/lance-tools/src/manifest.rs

Repository: lance-format/lance

Length of output: 5693


Keep the manifest module crate-private.

manifest is only reached from lance-tools CLI (crate::manifest::show_table_manifest), and the CLI entrypoint itself is pub(crate). Use pub(crate) mod manifest; unless lance_tools::manifest is intended as public API.

🤖 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-tools/src/lib.rs` at line 5, Change the manifest module
declaration to crate visibility by updating pub mod manifest to pub(crate) mod
manifest; in the crate root. Preserve internal access from the CLI entrypoint
through crate::manifest::show_table_manifest without exposing
lance_tools::manifest publicly.

Source: Coding guidelines

Comment on lines +34 to +41
pub struct LanceToolManifest {
path: Path,
dataset_base: Option<Path>,
raw_manifest: pb::Manifest,
manifest: Manifest,
indices: OptionalSection<Vec<IndexView>>,
transaction: OptionalSection<TransactionView>,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Prefer pub(crate) for this crate-internal type.

LanceToolManifest is only constructed and rendered within this module via the pub(crate) show_table_manifest entry point; exposing it as pub widens the crate's public API surface without a doc comment.

♻️ Proposed change
-pub struct LanceToolManifest {
+pub(crate) struct LanceToolManifest {

As per coding guidelines: "Prefer pub(crate) over pub for crate-internal items, and use pub use re-exports for the actual public API surface."

📝 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
pub struct LanceToolManifest {
path: Path,
dataset_base: Option<Path>,
raw_manifest: pb::Manifest,
manifest: Manifest,
indices: OptionalSection<Vec<IndexView>>,
transaction: OptionalSection<TransactionView>,
}
pub(crate) struct LanceToolManifest {
path: Path,
dataset_base: Option<Path>,
raw_manifest: pb::Manifest,
manifest: Manifest,
indices: OptionalSection<Vec<IndexView>>,
transaction: OptionalSection<TransactionView>,
}
🤖 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-tools/src/manifest.rs` around lines 34 - 41, Change the visibility
of the crate-internal LanceToolManifest struct from pub to pub(crate). Keep
show_table_manifest as the crate entry point and leave the struct’s fields and
behavior unchanged.

Source: Coding guidelines

Comment on lines +1105 to +1115
fn format_option_usize(value: Option<usize>) -> String {
value
.map(|value| value.to_string())
.unwrap_or_else(|| "unknown".to_string())
}

fn format_optional_usize(value: Option<usize>) -> String {
value
.map(|value| value.to_string())
.unwrap_or_else(|| "absent".to_string())
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

format_option_usize and format_optional_usize are indistinguishable by name.

The two differ only in the sentinel emitted for None ("unknown" vs "absent"), and neither name says which. A caller picking the wrong one silently mislabels absent data. Encode the sentinel in the name (or pass it as an argument).

♻️ Proposed change
-fn format_option_usize(value: Option<usize>) -> String {
-    value
-        .map(|value| value.to_string())
-        .unwrap_or_else(|| "unknown".to_string())
-}
-
-fn format_optional_usize(value: Option<usize>) -> String {
-    value
-        .map(|value| value.to_string())
-        .unwrap_or_else(|| "absent".to_string())
-}
+/// Formats a count that the manifest may not record; `None` renders as `unknown`.
+fn format_option_usize_or_unknown(value: Option<usize>) -> String {
+    value
+        .map(|value| value.to_string())
+        .unwrap_or_else(|| "unknown".to_string())
+}
+
+/// Formats an optional field; `None` renders as `absent`.
+fn format_option_usize_or_absent(value: Option<usize>) -> String {
+    value
+        .map(|value| value.to_string())
+        .unwrap_or_else(|| "absent".to_string())
+}

Callers to update: Lines 403, 409, 535 (unknown) and Lines 293, 305 (absent).

As per coding guidelines: "Use precise names describing what values are."

📝 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 format_option_usize(value: Option<usize>) -> String {
value
.map(|value| value.to_string())
.unwrap_or_else(|| "unknown".to_string())
}
fn format_optional_usize(value: Option<usize>) -> String {
value
.map(|value| value.to_string())
.unwrap_or_else(|| "absent".to_string())
}
/// Formats a count that the manifest may not record; `None` renders as `unknown`.
fn format_option_usize_or_unknown(value: Option<usize>) -> String {
value
.map(|value| value.to_string())
.unwrap_or_else(|| "unknown".to_string())
}
/// Formats an optional field; `None` renders as `absent`.
fn format_option_usize_or_absent(value: Option<usize>) -> String {
value
.map(|value| value.to_string())
.unwrap_or_else(|| "absent".to_string())
}
🤖 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-tools/src/manifest.rs` around lines 1105 - 1115, Rename
format_option_usize and format_optional_usize to explicitly identify their None
sentinels, such as unknown- and absent-specific names, then update callers at
the noted locations so unknown values use the “unknown” formatter and absent
values use the “absent” formatter.

Source: Coding guidelines

Comment on lines +1287 to +1338
#[tokio::test]
async fn test_parse_all_test_data_manifests() {
let test_data_dir = repo_root().join("test_data");
let mut manifest_paths = Vec::new();
collect_manifest_paths(&test_data_dir, &mut manifest_paths);
manifest_paths.sort();

assert!(
!manifest_paths.is_empty(),
"test_data should contain manifest compatibility samples"
);

for manifest_path in manifest_paths {
let args = LanceTableManifestArgs {
source: manifest_path.to_string_lossy().to_string(),
};
let manifest = LanceToolManifest::open(&args)
.await
.unwrap_or_else(|e| panic!("failed to parse {}: {}", manifest_path.display(), e));
if let OptionalSection::Error(error) = &manifest.indices {
panic!(
"failed to parse index section for {}: {}",
manifest_path.display(),
error
);
}
if let OptionalSection::Error(error) = &manifest.transaction {
panic!(
"failed to parse transaction for {}: {}",
manifest_path.display(),
error
);
}

let rendered = manifest.to_string();
assert!(
rendered.contains("manifest:"),
"rendered output missing manifest section for {}",
manifest_path.display()
);
assert!(
rendered.contains("schema:"),
"rendered output missing schema section for {}",
manifest_path.display()
);
assert!(
rendered.contains("fragments:"),
"rendered output missing fragments section for {}",
manifest_path.display()
);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Assertions do not actually verify the rendered report.

Checking for the literal headings manifest:, schema:, and fragments: passes even if every field value is wrong or every optional section degrades to absent — the test effectively only proves open() and Display did not panic. Since the report format is the feature, add at least one value-level assertion against a known checked-in manifest (version, fragment count, a data file path), or a snapshot comparison so format regressions surface.

As per coding guidelines: "Every bugfix and feature must have corresponding tests."

🤖 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-tools/src/manifest.rs` around lines 1287 - 1338, Strengthen
test_parse_all_test_data_manifests so it validates rendered manifest values
rather than only the manifest:, schema:, and fragments: headings. For at least
one known checked-in manifest, assert a stable value such as the version,
fragment count, or data-file path, or compare the rendered output against an
approved snapshot while preserving the existing compatibility checks.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-deps Dependency updates enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant