Skip to content

fix(search): FT._LIST enumerates both index stores, so a TEXT-only index is no longer invisible (#709) - #726

Merged
TinDang97 merged 1 commit into
mainfrom
fix/ft-list-both-stores-709
Aug 25, 2026
Merged

fix(search): FT._LIST enumerates both index stores, so a TEXT-only index is no longer invisible (#709)#726
TinDang97 merged 1 commit into
mainfrom
fix/ft-list-both-stores-709

Conversation

@TinDang97

@TinDang97 TinDang97 commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Closes #709.

The bug

An index whose schema carries no VECTOR field lives only in the TextStore. ft_list enumerated the vector store alone:

pub fn ft_list(store: &VectorStore, db_index: u8) -> Frame {
    let names = store.index_names_for_db(db_index);

so a TEXT-only index never appeared — even though FT.INFO and FT.SEARCH both work on it. The doc comment already claimed to return "all index names owned by the caller's currently SELECTed db", which is what it was documented to do and not what it did.

FT._LIST is how tools and the Moon Console discover indexes, so such an index could not be listed, inspected in a UI, or picked up by anything that enumerates before acting. It also silently breaks any harness that uses FT._LIST to verify index creation — the one that found this reported "built 0 indexes" after 50 successful FT.CREATEs.

The fix

Union the two stores by sort-then-dedup rather than a membership scan: that collapses the both-stores duplicate (an index carrying TEXT and VECTOR fields is registered in both and must appear exactly once) in O(n log n) instead of O(n²), and gives the result a stable order that neither store's hashing provides on its own.

The text_store reference is a parameter, not something reached for locally, so the compiler enumerated all seven call sites instead of letting one hide. Two of them bind the store under a different name in a branch only the runtime-tokio feature set compiles — the default build would have shipped that blind.

The sweep the issue asked for

Worth checking the same omission in any other place that enumerates indexes from one store only — FT.INFO's scatter/merge and FLUSHALL's index clearing both touch the pair.

Came back clean:

enumerator stores consulted
FT.INFO both (s.text_store.get_index_for_db alongside the vector lookup)
FLUSHALL / FLUSHDB both — auto_flush_indexes(&mut s.vector_store, &mut s.text_store, …)
replication/apply.rs:1059 both
spsc_handler.rs:3683 both
auto_hdel_vectors vector only, by design — its TEXT/TAG/NUMERIC counterpart is the separately documented follow-up in CLAUDE.md, not a new finding
FT._LIST vector only — the bug

Proving it

Lib test — red behaviourally, not by failing to compile: the signature was changed first with the body untouched.

left:  ["both", "vec"]
right: ["both", "txt", "vec"]

It guards its own premise, asserting txt really is in the text store and really is not in the vector store — otherwise the union would pass with nothing to union in.

E2E suite — run against the actual pre-fix binary (sha 3f058308 vs e07ee370), same failure. Both of its premise guards — FT.INFO and FT.SEARCH working on the TEXT-only index — pass on that same pre-fix binary, which is precisely the issue's complaint. It then restarts on the same dir, so the TEXT-only index has to come back from its sidecar and re-register.

Harnessscripts/test-commands.sh gains a TEXT-only row, and drops the index immediately afterwards on purpose: a later assertion checks FT._LIST is empty once testidx is gone, and now that FT._LIST can see TEXT-only indexes, leaving one behind would break a passing assertion.

Gates

  • cargo test --lib: 5053 passed, 0 failed
  • cargo test --test ft_list_both_stores_709: passes on the fixed binary, fails on the pre-fix one
  • clippy --all-targets: default / graph / runtime-tokio,jemalloc all clean
  • cargo fmt --check: clean
  • scripts/ci-local.sh + full hosted dispatch matrix: below

Summary by CodeRabbit

  • New Features

    • FT._LIST now includes both text and vector indexes.
    • Indexes shared by both types are listed only once, in stable order.
    • Listing remains scoped to the selected database and persists after restart.
  • Bug Fixes

    • Text-only indexes are now correctly discoverable through FT._LIST.
  • Tests

    • Added coverage for text-only, vector-only, and mixed indexes, including search, metadata, cleanup, and persistence scenarios.

@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 45e5b463-7657-438b-ab6f-737cd1885e19

📥 Commits

Reviewing files that changed from the base of the PR and between 0d42ffa and 43e4f03.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • scripts/test-commands.sh
  • src/command/vector_search/ft_admin.rs
  • src/command/vector_search/tests.rs
  • src/server/conn/handler_monoio/ft.rs
  • src/server/conn/handler_sharded/ft.rs
  • src/server/conn/handler_single.rs
  • src/shard/spsc_handler.rs
  • tests/ft_list_both_stores_709.rs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

FT._LIST now enumerates indexes from both vector and text stores, removes duplicates, and returns stable sorted results. All command paths pass TextStore. Unit, command, and restart-based integration tests cover the behavior.

Changes

FT._LIST dual-store enumeration

Layer / File(s) Summary
Union and deduplication
src/command/vector_search/ft_admin.rs, CHANGELOG.md
ft_list combines selected-database index names from VectorStore and TextStore, then sorts and deduplicates the result.
Handler store propagation
src/server/conn/handler_monoio/ft.rs, src/server/conn/handler_sharded/ft.rs, src/server/conn/handler_single.rs, src/shard/spsc_handler.rs
All single-shard, multi-shard, and SPSC FT._LIST paths pass the active TextStore.
Listing and persistence validation
src/command/vector_search/tests.rs, scripts/test-commands.sh, tests/ft_list_both_stores_709.rs
Tests cover text-only, vector-only, and mixed indexes, database scoping, deduplication, FT.INFO, FT.SEARCH, and restart persistence.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 43e4f

FT._LIST now includes text-only indexes while returning mixed text/vector indexes only once; the change is covered by passing tests and standard checks, so no actionable merge-blocking risk remains.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant FTList
  participant VectorStore
  participant TextStore
  Client->>FTList: Execute FT._LIST
  FTList->>VectorStore: Read names for selected database
  FTList->>TextStore: Read names for selected database
  FTList-->>Client: Return sorted deduplicated names
Loading

Suggested reviewers: pilotspacex-byte

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 11 functions across 8 files. (1 skipped: … 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 clearly identifies the primary fix: FT._LIST now enumerates both index stores and includes TEXT-only indexes.
Description check ✅ Passed The description provides a detailed summary, implementation rationale, issue scope, testing evidence, and performance rationale. It does not use every template heading, but it is substantially complet…
Linked Issues check ✅ Passed The changes satisfy issue #709 by listing TEXT-only, VECTOR-only, and mixed indexes exactly once, preserving database scoping and restart behavior. All affected call sites and requested verification p…
Out of Scope Changes check ✅ Passed The code, tests, harness update, and changelog entry are directly related to fixing and validating FT._LIST index enumeration. No unrelated code changes are identified.
Full details: Description check

Explanation

The description provides a detailed summary, implementation rationale, issue scope, testing evidence, and performance rationale. It does not use every template heading, but it is substantially complete.

Full details: Linked Issues check

Explanation

The changes satisfy issue #709 by listing TEXT-only, VECTOR-only, and mixed indexes exactly once, preserving database scoping and restart behavior. All affected call sites and requested verification paths are addressed.

Full details: Docstring Coverage

Explanation

Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 11 functions across 8 files. (1 skipped: 1 unsupported.)

✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ft-list-both-stores-709

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.

@TinDang97
TinDang97 force-pushed the fix/ft-list-both-stores-709 branch from 4a0aae0 to 43e4f03 Compare August 25, 2026 20:53
@TinDang97

Copy link
Copy Markdown
Collaborator Author

Force-pushed 43e4f033: the first ci-local run caught a real defect in my own test, not a flake — 3/3 deterministic failures on the VM tokio suite.

TRY 1 FAIL  moon command::vector_search::tests::test_ft_list_includes_text_only_indexes_moon709
TRY 2 FAIL  ...
TRY 3 FAIL  ...
  panicked at src/command/vector_search/tests.rs:4640:
  FT.CREATE must succeed for Some(BulkString(b"txt"))

That leg builds --no-default-features --features runtime-tokio,jemalloc, which drops text-index. Without it FT.CREATE ... SCHEMA body TEXT correctly answers ERR TEXT fields require the text-index feature, so the fixture had nothing to create. The test was wrong for that feature set; the production code was not — ft_list's union is gated the same way, so the un-featured build is correct by construction rather than untested. The test now carries #[cfg(feature = "text-index")], matching the integration suite's existing #![cfg(...)].

Worth recording because it is a gap in the usual local sweep: all four clippy legs passed first, including --no-default-features --features runtime-tokio,jemalloc --all-targets. Clippy only compiles the test, and an ungated #[cfg(test)] test compiles fine under every feature set — it just runs and fails. Only the running suite can catch this, which makes ci-local.sh the earliest gate that could.

Re-running the full bar now.

@TinDang97

Copy link
Copy Markdown
Collaborator Author

Dispatch run 32898881511 at 43e4f033 came back 8/9 with Check (Windows) red. That failure is not from this PR's diff.

The failing test is moon::compaction_escape_hatch_718 eh718_a_compaction_backlog_does_not_refuse_its_own_remedy, which belongs to #722 (moon#718) and touches no file this branch changes. It is a race in that fixture — the stall it builds is armed by the 1s MVCC sweep rather than by the write that creates the condition, so the fixture's remaining load races the sweep. Fast hosts win, the Windows runner loses, and the fixture's own writes come back MOONERR busy: compaction backlog.

Fixed test-only in #727, reproduced deterministically on macOS and proven non-vacuous there. Once #727 lands I will rebase this branch and re-dispatch.

…dex is no longer invisible (#709)

An index whose schema carries no VECTOR field lives only in the TextStore.
`ft_list` enumerated the vector store alone, so such an index never appeared
in FT._LIST — even though FT.INFO and FT.SEARCH both work on it. The doc
comment already claimed to return "all index names owned by the caller's
currently SELECTed db", which is what it was documented to do and not what it
did.

FT._LIST is how tools and the Moon Console discover indexes, so a TEXT-only
index could not be listed, inspected in a UI, or picked up by anything that
enumerates before acting. It also silently broke any harness that used
FT._LIST to verify index creation: the one that found this reported "built 0
indexes" after 50 successful FT.CREATEs.

The two stores are now unioned by sort-then-dedup rather than a membership
scan. That collapses the both-stores duplicate — an index carrying TEXT AND
VECTOR fields is registered in both and must appear exactly once — in
O(n log n) instead of O(n^2), and it gives the result a stable order that
neither store's hashing provides on its own.

The text_store reference was added as a PARAMETER rather than reached for
locally, so the compiler enumerated all seven call sites instead of letting
one hide. Two of them turned out to bind the store under a different name in
a branch only the tokio feature set compiles; the default build would have
shipped that blind.

The sweep the issue asked for came back clean. FT.INFO consults both stores
already, FLUSHALL/FLUSHDB index-clearing goes through auto_flush_indexes with
both, and the remaining find_matching_index_names_for_db callers either pair
the two stores or are deliberately vector-only (auto_hdel_vectors, whose
TEXT/TAG/NUMERIC counterpart is a separately documented follow-up). FT._LIST
was the only enumerator reading one store.

Proven against a pre-fix binary rather than assumed. The lib test is red
behaviourally, not by failing to compile: the signature was changed first with
the body untouched, giving `left: [both, vec]` against `right: [both, txt,
vec]`. The e2e suite fails the same way on main's binary (sha 3f058308 vs
e07ee370), and both of its premise guards — FT.INFO and FT.SEARCH working on
the TEXT-only index — pass on that same binary, which is exactly the issue's
point. The suite then restarts on the same dir, so the TEXT-only index has to
come back from its sidecar and re-register.

scripts/test-commands.sh gains a TEXT-only row. It drops the index
immediately afterwards on purpose: a later assertion checks FT._LIST is empty
once testidx is gone, and now that FT._LIST can see TEXT-only indexes,
leaving one behind would break a passing assertion.

Closes #709

author: Tin Dang
@TinDang97
TinDang97 force-pushed the fix/ft-list-both-stores-709 branch from 43e4f03 to 76912d4 Compare August 25, 2026 22:57
@TinDang97
TinDang97 merged commit 4a2f9e8 into main Aug 25, 2026
19 checks passed
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.

FT._LIST omits TEXT-only indexes (enumerates the vector store only)

1 participant