Skip to content

feat: add UserComparator::name() for stable identity persistence#101

Merged
polaz merged 19 commits into
mainfrom
feat/#74-feat-add-usercomparatorname-for-stable-identity-pe
Mar 23, 2026
Merged

feat: add UserComparator::name() for stable identity persistence#101
polaz merged 19 commits into
mainfrom
feat/#74-feat-add-usercomparatorname-for-stable-identity-pe

Conversation

@polaz
Copy link
Copy Markdown
Member

@polaz polaz commented Mar 22, 2026

Summary

  • Add name() -> &'static str method to UserComparator trait for stable comparator identity
  • Persist comparator name in tree manifest; check on reopen — mismatch returns Error::ComparatorMismatch
  • Backward compatible: trees created before this change default to "default" (matching DefaultUserComparator)

Technical Details

  • Comparator name written as comparator_name section in sfa archive during persist_version
  • SuperVersions stores comparator_name: Arc<str> so flush/compaction version upgrades include it without extra plumbing
  • Check runs in Tree::recover after manifest decode, before any data access
  • Follows RocksDB Comparator::Name() pattern (requested in feat: custom key comparison / comparator #67 review)

Test Plan

  • Reopen with same comparator succeeds
  • Reopen with different custom comparator fails with ComparatorMismatch
  • Reopen custom-comparator tree with default comparator fails
  • Reopen default-comparator tree with default comparator succeeds
  • All existing tests pass (429 unit + integration)

Closes #74

Summary by CodeRabbit

  • New Features

    • Tree comparators are now persisted and automatically validated when reopening a tree.
  • Bug Fixes

    • Attempting to reopen a tree with an incompatible comparator now fails with a clear error message.
  • Tests

    • Added comprehensive tests for comparator persistence and validation behavior.

Copilot AI review requested due to automatic review settings March 22, 2026 18:55
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 22, 2026

Warning

Rate limit exceeded

@polaz has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 16 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 90f2a73c-f4ec-42eb-92da-7b288a1936ff

📥 Commits

Reviewing files that changed from the base of the PR and between ae88449 and 5efe609.

📒 Files selected for processing (11)
  • src/comparator.rs
  • src/config/mod.rs
  • src/error.rs
  • src/manifest.rs
  • src/table/util.rs
  • src/tree/inner.rs
  • src/tree/mod.rs
  • src/version/mod.rs
  • src/version/persist.rs
  • src/version/super_version.rs
  • tests/custom_comparator.rs
📝 Walkthrough

Walkthrough

This PR implements comparator identity persistence and validation for tree recovery. It adds a name() method to the UserComparator trait, persists the comparator name in the tree manifest during creation, and validates on tree reopen that the supplied comparator name matches the persisted one, failing with ComparatorMismatch if they differ.

Changes

Cohort / File(s) Summary
Core trait and error handling
src/comparator.rs, src/error.rs
Added name(&self) -> &'static str method to UserComparator trait and implemented for DefaultUserComparator returning "default". New Error::ComparatorMismatch { stored: String, supplied: &'static str } variant to signal comparator name mismatches on tree reopen.
Manifest and version persistence
src/manifest.rs, src/version/mod.rs, src/version/persist.rs
Added comparator_name: String field to Manifest, populated during decode_from (defaulting to "default" if absent, max 256 bytes). Updated Version::encode_into and persist_version to accept and serialize comparator name with length validation.
Version management refactoring
src/version/super_version.rs
Refactored SuperVersions from tuple struct to named struct with versions: VecDeque<SuperVersion> and comparator_name: Arc<str> field. Updated all accessors, iteration logic, and persistence calls to use new structure. Added test helper for construction.
Tree recovery and initialization
src/tree/inner.rs, src/tree/mod.rs
TreeInner::create_new now passes config.comparator.name() to persist_version. Tree::recover validates persisted comparator name matches supplied config.comparator.name(), returning ComparatorMismatch error on mismatch.
Documentation update
src/config/mod.rs
Updated Config::comparator() doc comment to replace warning about non-persistence with specification of new behavior: name is persisted and validated on reopen.
Test implementations
src/table/util.rs, tests/custom_comparator.rs
Added name() implementations to ReverseComparator (test-only) and custom comparators in test suite. Extended test coverage with reopen scenarios: same comparator reopen succeeds, different comparator reopen fails with ComparatorMismatch, missing comparator on custom-comparator tree fails, default comparator reopen succeeds.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Config
    participant TreeInner
    participant VersionPersist
    participant Manifest
    participant TreeRecover
    
    rect rgba(100, 150, 200, 0.5)
    Note over Client,Manifest: Tree Creation Flow
    Client->>Config: new with custom comparator
    Config->>TreeInner: create_new
    TreeInner->>VersionPersist: persist_version with comparator.name()
    VersionPersist->>Manifest: encode comparator_name field
    Manifest->>Manifest: write to manifest metadata
    end
    
    rect rgba(200, 100, 100, 0.5)
    Note over Client,TreeRecover: Tree Reopen Flow
    Client->>TreeRecover: open tree with comparator
    TreeRecover->>Manifest: decode_from (read comparator_name)
    Manifest-->>TreeRecover: return stored comparator_name
    TreeRecover->>TreeRecover: compare supplied.name() vs stored
    alt Names Match
        TreeRecover-->>Client: success, tree recovered
    else Names Mismatch
        TreeRecover-->>Client: Error::ComparatorMismatch
    end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A comparator's name now etched in stone,
No silent mismatches, the tree holds its own!
When reopening paths, names must align—
A safeguard woven through manifest design. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main feature: adding a name() method to UserComparator for persistent stable identity.
Linked Issues check ✅ Passed All coding requirements from issue #74 are fully implemented: UserComparator::name() method added, comparator name persisted in manifest, ComparatorMismatch error introduced, and validation implemented on tree reopen with comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #74 objectives: adding the name method, persistence, error handling, and comprehensive test coverage. No unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#74-feat-add-usercomparatorname-for-stable-identity-pe

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

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 22, 2026

Codecov Report

❌ Patch coverage is 95.73171% with 7 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/comparator.rs 76.92% 3 Missing ⚠️
src/version/super_version.rs 88.46% 3 Missing ⚠️
src/tree/mod.rs 93.75% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a stable, persisted identity for key comparators so trees can refuse to open with an incompatible UserComparator, preventing silent corruption when comparator configuration changes across open/close cycles.

Changes:

  • Extend UserComparator with name() -> &'static str and implement it for the default comparator and test comparators.
  • Persist comparator_name into the version/manifest SFA archive during persist_version, and plumb it through SuperVersions so subsequent version writes keep the same identity.
  • On Tree::recover, decode the stored comparator name and return Error::ComparatorMismatch if it differs from the supplied comparator’s name; add integration tests covering reopen success/failure cases.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/custom_comparator.rs Adds comparator name() implementations and reopen/mismatch integration tests.
src/comparator.rs Extends the public comparator trait with name() and documents persisted identity behavior.
src/config/mod.rs Updates Config::comparator docs to reflect persisted name + mismatch failure.
src/error.rs Introduces Error::ComparatorMismatch { stored, supplied }.
src/manifest.rs Decodes optional comparator_name from manifest with a legacy fallback to "default".
src/tree/mod.rs Enforces comparator-name match during recovery before accessing any data.
src/tree/inner.rs Persists comparator name when creating the initial version file.
src/version/mod.rs Writes comparator_name into the version/manifest encoding.
src/version/persist.rs Threads comparator_name through persist_version into Version::encode_into.
src/version/super_version.rs Stores comparator name for subsequent version upgrades and passes it to persistence.
src/table/util.rs Updates internal test comparator implementation to satisfy the new trait method.

Comment thread src/comparator.rs
Comment thread src/manifest.rs
Comment thread src/manifest.rs
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Comment thread src/version/persist.rs
Comment thread src/manifest.rs
Comment thread src/manifest.rs Outdated
Comment thread src/manifest.rs
@polaz polaz force-pushed the feat/#74-feat-add-usercomparatorname-for-stable-identity-pe branch from 296df9d to cf3b2f0 Compare March 22, 2026 19:53
@polaz polaz requested a review from Copilot March 22, 2026 20:06
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Comment thread src/config/mod.rs Outdated
Comment thread src/error.rs Outdated
Comment thread src/version/persist.rs Outdated
@polaz polaz force-pushed the feat/#74-feat-add-usercomparatorname-for-stable-identity-pe branch from 2d1bed3 to ae88449 Compare March 22, 2026 20:19
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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)
src/tree/mod.rs (1)

1340-1346: ⚠️ Potential issue | 🟠 Major

Validate the comparator before calling recover_levels().

recover_levels() already uses config.comparator during table recovery and orphan cleanup, so a reopen that is going to fail at Lines 1370-1381 still does comparator-sensitive work and can delete files first. Move the manifest decode/name check ahead of this call so rejected opens are side-effect free.

Also applies to: 1370-1381

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tree/mod.rs` around lines 1340 - 1346, Move the comparator/manifest
validation earlier so no side-effecting work runs before an open is rejected:
before calling Self::recover_levels(...) validate the manifest decode and that
config.comparator matches the manifest/name check (the same checks currently
around the reopen logic at the block handling Lines 1370-1381), and only call
recover_levels (which uses config.comparator) after those validations succeed;
update the code paths around recover_levels and the reopen logic (referencing
recover_levels, config.comparator, and the manifest decode/name check) so
rejected opens return early without invoking recover_levels or performing
orphan/table cleanup.
🧹 Nitpick comments (1)
src/manifest.rs (1)

169-210: Good test coverage for comparator name handling.

The tests verify the three critical scenarios:

  1. Missing section defaults to "default"
  2. Present section round-trips correctly
  3. Oversized section is rejected

Consider adding a test for invalid UTF-8 bytes in the comparator_name section to verify the Error::Utf8 path.

🧪 Optional: Add invalid UTF-8 test
#[test]
fn manifest_rejects_invalid_utf8_comparator_name() -> crate::Result<()> {
    let dir = tempfile::tempdir()?;
    let path = dir.path().join("manifest");

    // Write manifest with invalid UTF-8 bytes directly
    let file = std::fs::File::create(&path)?;
    let mut writer = sfa::Writer::from_writer(std::io::BufWriter::new(file));

    writer.start("format_version")?;
    writer.write_u8(FormatVersion::V4.into())?;

    writer.start("tree_type")?;
    writer.write_u8(TreeType::Standard.into())?;

    writer.start("level_count")?;
    writer.write_u8(7)?;

    writer.start("filter_hash_type")?;
    writer.write_u8(u8::from(ChecksumType::Xxh3))?;

    writer.start("comparator_name")?;
    // Invalid UTF-8 sequence
    writer.write_all(&[0xFF, 0xFE])?;

    writer.finish().map_err(|e| match e {
        sfa::Error::Io(e) => crate::Error::from(e),
        _ => unreachable!(),
    })?;

    let reader = sfa::Reader::new(&path)?;
    let result = Manifest::decode_from(&path, &reader);
    assert!(
        matches!(result, Err(crate::Error::Utf8(_))),
        "expected Utf8 error"
    );
    Ok(())
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manifest.rs` around lines 169 - 210, Add a test that writes a manifest
with an invalid UTF-8 byte sequence in the "comparator_name" section using
sfa::Writer (write the required sections: format_version, tree_type,
level_count, filter_hash_type, then start "comparator_name" and write invalid
bytes like 0xFF, 0xFE), open it with sfa::Reader and call
Manifest::decode_from(&path, &reader), and assert the result matches
Err(crate::Error::Utf8(_)); reference Manifest::decode_from, sfa::Writer, and
the "comparator_name" section to locate where to create the invalid-UTF8 test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/version/persist.rs`:
- Around line 13-21: The code currently panics with assert! in persist_version
when comparator_name.len() > MAX_COMPARATOR_NAME_LEN; replace the assert with a
runtime validation that returns an Err via the existing crate::Result return
type. In persist_version, check if comparator_name.len() >
MAX_COMPARATOR_NAME_LEN and return an appropriate crate::Result::Err (construct
an error using your crate's error type or helper) containing a clear message
that the comparator name is too long and include comparator_name and
MAX_COMPARATOR_NAME_LEN for context, instead of panicking.

---

Outside diff comments:
In `@src/tree/mod.rs`:
- Around line 1340-1346: Move the comparator/manifest validation earlier so no
side-effecting work runs before an open is rejected: before calling
Self::recover_levels(...) validate the manifest decode and that
config.comparator matches the manifest/name check (the same checks currently
around the reopen logic at the block handling Lines 1370-1381), and only call
recover_levels (which uses config.comparator) after those validations succeed;
update the code paths around recover_levels and the reopen logic (referencing
recover_levels, config.comparator, and the manifest decode/name check) so
rejected opens return early without invoking recover_levels or performing
orphan/table cleanup.

---

Nitpick comments:
In `@src/manifest.rs`:
- Around line 169-210: Add a test that writes a manifest with an invalid UTF-8
byte sequence in the "comparator_name" section using sfa::Writer (write the
required sections: format_version, tree_type, level_count, filter_hash_type,
then start "comparator_name" and write invalid bytes like 0xFF, 0xFE), open it
with sfa::Reader and call Manifest::decode_from(&path, &reader), and assert the
result matches Err(crate::Error::Utf8(_)); reference Manifest::decode_from,
sfa::Writer, and the "comparator_name" section to locate where to create the
invalid-UTF8 test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 94127766-c8a8-43d8-8f42-84e476a094de

📥 Commits

Reviewing files that changed from the base of the PR and between ec76236 and ae88449.

📒 Files selected for processing (11)
  • src/comparator.rs
  • src/config/mod.rs
  • src/error.rs
  • src/manifest.rs
  • src/table/util.rs
  • src/tree/inner.rs
  • src/tree/mod.rs
  • src/version/mod.rs
  • src/version/persist.rs
  • src/version/super_version.rs
  • tests/custom_comparator.rs

Comment thread src/tree/mod.rs Outdated
Comment thread src/version/persist.rs Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Comment thread src/version/persist.rs Outdated
Comment thread src/manifest.rs
Comment thread src/tree/mod.rs
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comment thread src/tree/mod.rs Outdated
@polaz polaz force-pushed the feat/#74-feat-add-usercomparatorname-for-stable-identity-pe branch from 91a9dae to 73de2ae Compare March 22, 2026 21:46
@polaz polaz requested a review from Copilot March 22, 2026 22:06
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Comment thread src/version/persist.rs Outdated
Comment thread src/table/util.rs Outdated
Copy link
Copy Markdown

@sw-release-bot sw-release-bot Bot left a comment

Choose a reason for hiding this comment

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

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'lsm-tree db_bench'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.15.

Benchmark suite Current: 0555155 Previous: 2c7d5dd Ratio
fillrandom 865687.9591046238 ops/sec 1012070.7707286807 ops/sec 1.17
mergerandom 600997.8818580902 ops/sec 693381.2502304625 ops/sec 1.15

This comment was automatically generated by workflow using github-action-benchmark.

CC: @polaz

@polaz polaz force-pushed the feat/#74-feat-add-usercomparatorname-for-stable-identity-pe branch from 0555155 to b927cd9 Compare March 22, 2026 22:27
@polaz polaz requested a review from Copilot March 22, 2026 22:29
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Comment thread src/version/persist.rs
Comment thread src/tree/mod.rs
polaz added 16 commits March 23, 2026 02:35
Cover the three paths in Manifest::decode_from:
- missing section defaults to "default"
- present section round-trips correctly
- oversized section (>256 bytes) is rejected
- Assert comparator_name <= 256 bytes in persist_version (symmetric
  with the read-side bound in Manifest::decode_from)
- Replace unwrap() with ? in manifest unit tests (clippy lint)
- Clarify backward-compat comment: custom comparators cannot exist
  in pre-change manifests because UserComparator was introduced in
  the same release cycle
Cover the name() and is_lexicographic() methods directly to satisfy
codecov patch coverage threshold.
Use `crate::UserComparator::name` in doc comments within config and
error modules where UserComparator is not directly in scope.
UserComparator::name() returns &'static str — an oversized name is
a programmer error, not a runtime condition. Assert is appropriate.
Move comparator name and format version checks ahead of
recover_levels() so a rejected open is side-effect free.
recover_levels loads tables and cleans up orphans — running it
before validation wastes work and can modify disk state for an
open that will be rejected anyway.
Cover the Error::Utf8 path when comparator_name section contains
non-UTF-8 bytes.
The test-only ReverseComparator's name() method was never called,
leaving it uncovered in the patch diff.
- Extract shared MAX_COMPARATOR_NAME_BYTES constant to comparator
  module, replacing duplicated literals in persist.rs and manifest.rs
- Downgrade comparator mismatch log from error to warn — this is a
  user-correctable misconfiguration, not an internal error
- MAX_COMPARATOR_NAME_BYTES: pub(crate) → pub (clippy::redundant_pub_crate)
- Pre-recovery validation scope: remove tree_type from list (checked post-recovery)
Replace assert! with an Err return for comparator names exceeding
MAX_COMPARATOR_NAME_BYTES, giving callers a recoverable error
instead of a process crash.
Exercise the persist_version error path for comparator names
exceeding MAX_COMPARATOR_NAME_BYTES (256 bytes).
@polaz polaz force-pushed the feat/#74-feat-add-usercomparatorname-for-stable-identity-pe branch from 9f4e2b2 to 27e5c0e Compare March 23, 2026 00:36
@polaz polaz requested a review from Copilot March 23, 2026 00:41
@polaz
Copy link
Copy Markdown
Member Author

polaz commented Mar 23, 2026

@coderabbitai re-review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 23, 2026

@polaz Sure, I'll re-review the PR now!


🧠 Learnings used
Learnt from: polaz
Repo: structured-world/lsm-tree PR: 101
File: src/tree/mod.rs:1370-1381
Timestamp: 2026-03-22T20:30:38.753Z
Learning: In `structured-world/lsm-tree`, the `UserComparator` trait (and all custom-comparator support) was introduced in PR `#67` within the same release cycle as the comparator-name persistence added in PR `#101`. All trees created before PR `#67` used only `DefaultUserComparator` (whose `name()` returns `"default"`). Manifests without a `comparator_name` section default to `"default"` in `Manifest::decode_from` (src/manifest.rs). Therefore, there are no legacy trees with a custom comparator and no upgrade path is needed — do not flag the missing-section fallback to `"default"` as a legacy-compatibility risk in PR `#101` or future PRs.

Learnt from: polaz
Repo: structured-world/lsm-tree PR: 67
File: src/comparator.rs:7-33
Timestamp: 2026-03-22T11:14:39.924Z
Learning: In `structured-world/lsm-tree` (`src/comparator.rs`), the `UserComparator` trait introduced in PR `#67` intentionally has no stable persisted identity (no `name()` method). Comparator identity persistence and mismatch detection on tree reopen is deferred to issue `#74`. The current design follows RocksDB's approach of placing responsibility on the caller (documented in `src/comparator.rs` lines 31–33 and in `Config::comparator` doc). Do not flag the absence of `UserComparator::name()` or comparator identity persistence as a blocking concern in PR `#67` or future PRs until issue `#74` is addressed.

Learnt from: polaz
Repo: structured-world/lsm-tree PR: 101
File: src/tree/mod.rs:1370-1381
Timestamp: 2026-03-22T20:38:37.516Z
Learning: In `structured-world/lsm-tree`, the `UserComparator` trait was introduced in PR `#67` in the same release cycle as comparator-name persistence (PR `#101`, closes issue `#74`). All trees created before PR `#101` used `DefaultUserComparator` (no custom-comparator tree could have existed without the trait). Therefore, the manifest fallback of `"default"` for a missing `comparator_name` section is safe: it matches every pre-existing tree exactly, and no legacy custom-comparator trees exist that would be incorrectly rejected on reopen. Do not flag the missing-section → `"default"` fallback as an unsafe upgrade path.

Learnt from: polaz
Repo: structured-world/lsm-tree PR: 67
File: src/memtable/mod.rs:72-77
Timestamp: 2026-03-22T12:26:47.795Z
Learning: In `structured-world/lsm-tree` (`src/memtable/interval_tree.rs` and `src/memtable/mod.rs`), the memtable `IntervalTree` used for range tombstone containment queries (`is_key_suppressed_by_range_tombstone`, `query_suppression`, `insert_range_tombstone`) still uses lexicographic `Ord` on `UserKey` rather than the pluggable `SharedComparator` introduced in PR `#67`. Threading the comparator into the AVL interval tree is a non-trivial refactor intentionally deferred from PR `#67` and tracked in issue `#81`. Do not flag the absence of comparator-aware RT suppression in the memtable interval tree as a blocking concern in PR `#67` or future PRs until issue `#81` is addressed.

Learnt from: polaz
Repo: structured-world/lsm-tree PR: 101
File: src/version/persist.rs:13-21
Timestamp: 2026-03-22T20:30:38.488Z
Learning: In `structured-world/lsm-tree` (`src/version/persist.rs`), the `assert!` in `persist_version` that enforces `comparator_name.len() <= MAX_COMPARATOR_NAME_LEN` (256 bytes) is intentional and appropriate. `UserComparator::name()` returns `&'static str`, a compile-time constant, so an oversized name is a programmer error in the `UserComparator` implementor — not runtime user input. Panicking (rather than returning `Err`) is the correct design, matching the same pattern as `assert_eq!(7, level_count)` in manifest decoding. Do not flag this assert as a missing `Result`-returning validation.

Learnt from: polaz
Repo: structured-world/lsm-tree PR: 117
File: src/tree/ingest.rs:325-330
Timestamp: 2026-03-22T22:44:14.193Z
Learning: In `structured-world/lsm-tree` (`src/tree/ingest.rs`), the write-path monotonicity guards in `Ingestion::write`, `write_tombstone`, `write_weak_tombstone`, and `write_indirection` use plain lexicographic `key > *prev` comparisons instead of the configured `UserComparator`. This is a pre-existing issue acknowledged as valid but intentionally out of scope for PR `#98` (issue `#98` covers `Run::push()` and related methods). It is tracked in a separate follow-up issue. Do not flag the lexicographic ordering guards in `src/tree/ingest.rs` ingestion write paths as a concern in PR `#98` or future PRs until that separate issue is addressed.

Learnt from: polaz
Repo: structured-world/lsm-tree PR: 100
File: tests/custom_comparator_compaction.rs:421-447
Timestamp: 2026-03-22T20:07:10.672Z
Learning: In `structured-world/lsm-tree` (`tests/custom_comparator_compaction.rs`), the two range-scan integration tests (`reverse_comparator_range_scan_after_compaction` and the second range-scan test) are intentionally `#[ignore]`d in PR `#100` because `RunReader::new()` still uses lexicographic `range_overlap_indexes` rather than the comparator-aware variant for on-disk range reads. Threading the `SharedComparator` through `RunReader` is a known deferred limitation documented in the PR body and tracked as a follow-up. Do not flag the ignored range-scan tests or the absence of comparator-aware `RunReader` overlap logic as a blocking concern in PR `#100` or future PRs until that follow-up issue is addressed.

Learnt from: polaz
Repo: structured-world/lsm-tree PR: 0
File: :0-0
Timestamp: 2026-03-22T09:29:48.222Z
Learning: In `structured-world/lsm-tree` (`src/prefix.rs`), `is_valid_scan_boundary(&self, prefix: &[u8]) -> bool` has been added to the `PrefixExtractor` trait in PR `#68` (closes issue `#47`), replacing the previously internal `is_valid_prefix_boundary` name. The method has a default implementation that checks `prefixes(prefix).any(|p| p == prefix)`, correct for sub-slice extractors, and can be overridden for O(1) checks (e.g., `FixedLengthPrefix`) or for extractors whose `prefixes()` does not return sub-slices. `compute_prefix_hash` in `src/prefix.rs` calls `is_valid_scan_boundary` as the sole call site. Do not flag the absence of `is_valid_scan_boundary` or suggest adding it in future PRs — it is already part of the public API.
✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

Use ? directly — From<sfa::Error> for crate::Error is implemented,
no need for manual map_err with unreachable branch.
@polaz polaz merged commit ff82717 into main Mar 23, 2026
15 of 16 checks passed
@polaz polaz deleted the feat/#74-feat-add-usercomparatorname-for-stable-identity-pe branch March 23, 2026 01:31
@sw-release-bot sw-release-bot Bot mentioned this pull request Mar 23, 2026
polaz added a commit that referenced this pull request Mar 23, 2026
Required after #101 added name() as a required trait method.
polaz added a commit that referenced this pull request Mar 23, 2026
Required after #101 added name() as a required trait method.
polaz added a commit that referenced this pull request Mar 23, 2026
Required after #101 added name() as a required trait method.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Add UserComparator::name() for stable identity persistence and mismatch detection on tree reopen

2 participants