Commit Graph

1997 Commits

Author SHA1 Message Date
qdrant-cloud-bot
666912ea9d test: failing test for values_per_hash drift on duplicate geo point removal (#9043)
* test: add failing test for values_per_hash drift on duplicate geo point removal

Reproduces the bug where `remove_point` only calls `decrement_hash_value_counts`
once per unique geohash, while `add_many_geo_points` increments it once per value.
When a point has duplicate geo coordinates (same geohash), the counters drift
upward permanently after removal.

Ref: https://github.com/qdrant/qdrant/pull/9033#discussion_r3241154045
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: decrement values_per_hash once per value on geo point removal

`add_many_geo_points` increments `values_per_hash` once per value, but
`remove_point` deduplicated geohashes with a HashSet + `continue` that
also skipped the per-value decrement. A point with duplicate geo
coordinates (same geohash) therefore left the counters drifted upward
permanently after removal.

Move `decrement_hash_value_counts` above the dedup guard so it runs once
per value, matching the increment side. `points_map` and
`points_per_hash` track points, not values, so they stay deduplicated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: generall <andrey@vasnetsov.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 23:30:39 +02:00
Daniel Boros
9ee69b476a refactor: read-only-fulltext (#9035)
* refactor: read only full text

* fix: linter

* refactor: follow new file structure

* chore: remove storage enums

* fix: linter

* fix: FullTextReadIndex methods

* chore: remove duplicated impls

* fix: format

* refactor: unify FullTextIndex telemetry via trait default method

Add a `get_telemetry_data` default method to `FullTextIndexRead` built
from the existing telemetry methods, so the `ReadOnlyFieldIndex` match
arm is consistent with every other variant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: move full-text lifecycle methods off FullTextIndexRead trait

`populate`, `clear_cache`, `files` and `immutable_files` are lifecycle
concerns, not part of the read surface. Move them from the
`FullTextIndexRead` trait to inherent methods alongside the rest of each
index's lifecycle code (open / wipe / flusher). Also split the
`mutable_text_index` tests into their own `tests.rs`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: restructure full_text_index module layout

Move the `FullTextIndex` enum into `full_text_index/mod.rs` and split its
impls following the inner-module convention: lifecycle (constructors,
builders, ValueIndexer, PayloadFieldIndex) into `lifecycle.rs`, read-path
impls and shared free functions into `read_ops.rs`. Extract the
`FullTextIndexRead` trait into its own `full_text_index_read.rs`.

Drops the inherent `FullTextIndex::get_telemetry_data`, which duplicated
the `FullTextIndexRead::get_telemetry_data` default method.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: read-only counterpart of MutableFullTextIndex

Extract the shared in-memory state of `MutableFullTextIndex` into
`MutableFullTextIndexInner` (inverted index, config, tokenizer), which
implements `FullTextIndexRead`. `MutableFullTextIndex` now wraps it
alongside a writable `Gridstore`.

Add `ReadOnlyAppendableFullTextIndex<S>`, wrapping the same inner state
with a `GridstoreReader` over generic `UniversalRead`. Mirrors the
`MutableMapIndex` / `ReadOnlyAppendableMapIndex` split; loading and
lifecycle land in a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: turn ReadOnlyFullTextIndex into a two-variant enum

Rename the `read_only_text_index` module to `read_only` and reshape
`ReadOnlyFullTextIndex` from a thin `MmapFullTextIndex` wrapper into an
`Appendable` / `Immutable` enum, mirroring `ReadOnlyMapIndex`. Its
`FullTextIndexRead` impl is now a dispatcher forwarding to the active
variant.

Drop the no-op inherent `populate` (mutable, immutable) and
`immutable_files` (mutable) methods that tripped `clippy::unused_self`
and `clippy::unnecessary_wraps` after the earlier trait-to-inherent
move; `FullTextIndex::populate` inlines the in-RAM no-op directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: generall <andrey@vasnetsov.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 23:10:53 +02:00
Luis Cossío
ee739aff79 [UIO] Introduce Populate enum (#8946)
* introduce `Populate` enum

* fix: apply CodeRabbit auto-fixes

Fixed 6 file(s) based on 6 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

* fix coderabbit fix

* Use from rather than into

* add `Auto` variant

* fix rebase

* fix rebase again

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
Co-authored-by: timvisee <tim@visee.me>
2026-05-14 14:49:09 -04:00
Andrey Vasnetsov
5436eb3bde refactor: read-only numeric index (#9038)
* refactor: split numeric index variants into dedicated modules

Move MutableNumericIndex, ImmutableNumericIndex, and MmapNumericIndex
into their own directories, each split into mod.rs (struct definitions),
lifecycle.rs (open/build/wipe/mutations), and read_ops.rs (accessors),
mirroring the map_index layout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: drop single-variant Storage enums in mutable/immutable numeric index

Replace `Storage<T>` wrappers around the only backing store with the
store types directly: `Gridstore<Vec<T>>` for `MutableNumericIndex` and
`Box<MmapNumericIndex<T>>` for `ImmutableNumericIndex`. Collapses the
trivial single-arm matches into direct method calls.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: introduce NumericIndexRead trait

Mirror `MapIndexRead` from the map_index refactor: define a
`NumericIndexRead<T>` trait in `numeric_index/read_ops.rs` and implement
it on each of the three storage variants
(`MutableNumericIndex`, `ImmutableNumericIndex`, `MmapNumericIndex`).

Trait signatures are unified across variants — in-memory variants accept
and ignore the `hw_counter` argument that the mmap-backed variant uses
for IO tracking, and `total_unique_values_count`, `values_range`, and
`orderable_values_range` return `OperationResult` everywhere so the
dispatcher in `NumericIndexInner` can call them generically.

Variant-specific helpers that don't fit the shared shape stay as
inherent methods: `MutableNumericIndex::map()`,
`ImmutableNumericIndex::values_range_size()`, and
`MmapNumericIndex::{values_range_size, is_on_disk}`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: add ReadOnlyAppendableNumericIndex

Counterpart to `MutableNumericIndex`, mirroring `ReadOnlyAppendableMapIndex`
from the map_index refactor. It reuses the shared `InMemoryNumericIndex`
in-memory state but is backed by a `GridstoreReader` over generic
`UniversalRead` instead of a writable `Gridstore`, and implements
`NumericIndexRead` by forwarding to the in-memory index — no mutation
surface.

Loading / lifecycle (constructor, files, populate, clear_cache) will
follow in a separate change; the storage field is held only to pin the
on-disk layout for now.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: rename MmapNumericIndex to UniversalNumericIndex, expose UniversalRead param

Mirror `UniversalMapIndex`: the type is now generic over `S: UniversalRead`
with a `MmapFile` default, so the index can be served from any
`UniversalRead` backend (io_uring, disk-cache wrappers, …) rather than
the hard-coded `MmapFile`.

The `NumericIndexRead` impl and read-side helpers are generic over `S`;
`build` / `open` and the other lifecycle methods stay `MmapFile`-only
since they construct mmap-backed storage from a path. The
`NumericIndexInner::Mmap` enum variant keeps its name and uses the
default `S = MmapFile`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: split numeric_index/storage/mod.rs into lifecycle and read_ops

`storage/mod.rs` now holds only the `NumericIndexInner` enum and module
wiring. The variant-dispatch impls are split into sibling modules
matching the layout of the individual storage variants:

- `lifecycle.rs`: construction, persistence, file listing, cache
  control, and `remove_point`.
- `read_ops.rs`: read-path forwarding — value lookups, telemetry, RAM
  accounting, `is_on_disk`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: implement NumericIndexRead for NumericIndexInner

The enum-level read dispatch was a set of inherent methods scattered
across storage/read_ops.rs and storage/statistics.rs with signatures
that drifted from the variant trait (`values_count` returned `usize`,
`max_values_per_point` vs `get_max_values_per_point`, a hand-rolled
`get_telemetry_data`). Make `NumericIndexInner` implement
`NumericIndexRead` directly so it shares one interface with the three
storage variants.

- All 12 trait methods are forwarded via match dispatch in
  storage/read_ops.rs; `values_range` / `orderable_values_range` box
  the per-variant iterators.
- `get_histogram`, `get_points_count`, `total_unique_values_count` move
  out of statistics.rs into the trait impl; `values_is_empty` and
  `get_telemetry_data` now come from the trait defaults.
- `point_ids_by_value` and `is_on_disk` stay as enum-only inherent
  helpers (not part of the shared trait).
- Callers updated: `NumericIndex::values_count` unwraps the now
  `Option`-returning trait method; `filter` boxes `point_ids_by_value`;
  `field_index.rs` and `numeric_field_index.rs` import the trait.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: add ReadOnlyNumericIndexInner

Read-only counterpart to `NumericIndexInner`, mirroring `ReadOnlyMapIndex`
from the map_index refactor. Lives under
`numeric_index/storage/read_only` and selects across the two read-only
storage backends:

- `Appendable(ReadOnlyAppendableNumericIndex<T, S>)` — loaded into RAM
  from the appendable Gridstore format.
- `Immutable(UniversalNumericIndex<T, S>)` — served directly from the
  immutable stored format.

Implements `NumericIndexRead` by forwarding each method to the active
variant; `values_is_empty` / `get_telemetry_data` come from the trait
defaults.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: rename numeric_index read_ops to numeric_index_read, split mod.rs

Two changes:

- Rename `numeric_index/read_ops.rs` (the `NumericIndexRead` trait
  definition) to `numeric_index_read.rs`, freeing the `read_ops` name.
- Split the leftover content of `numeric_index/mod.rs` into sibling
  modules, matching the per-variant layout:
  - `lifecycle.rs`: the `Encodable` key-format trait + impls and the
    `HISTOGRAM_*` construction constants.
  - `read_ops.rs`: the `StreamRange` trait and the `Range` →
    index-key-bounds conversion.

`mod.rs` now only wires modules and re-exports. `Encodable` and
`StreamRange` keep their public paths via re-export; `tests.rs` gains
explicit imports for the symbols it previously picked up through the
`mod.rs` glob.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: dissolve numeric_index/index.rs into mod, lifecycle, read_ops

`index.rs` only held the `NumericIndex` wrapper and its two impl blocks;
spread them to match the per-module layout used elsewhere:

- `NumericIndex` struct + `NumericIndexIntoInnerValue` trait → `mod.rs`
  (type definitions live with the module wiring).
- The inherent `impl NumericIndex` (open / build / cache control /
  storage introspection) → `lifecycle.rs`, alongside the `HISTOGRAM_*`
  seed constants.
- The `PayloadFieldIndexRead` impl → `read_ops.rs`.

Also move the `Encodable` key-format trait out of `lifecycle.rs` into
its own `encodable.rs`. `mod.rs` keeps re-exporting `Encodable` so its
public path is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: add ReadOnlyNumericIndex with NumericIndexRead + PayloadFieldIndexRead

Read-only counterpart to `NumericIndex`, wrapping `ReadOnlyNumericIndexInner`
plus the payload value type parameter `P`. Implements both `NumericIndexRead`
and `PayloadFieldIndexRead` by forwarding to the inner storage-variant enum.

To support `PayloadFieldIndexRead` without duplicating the query logic, the
cardinality/filter/payload-block/condition-checker code is extracted into a
new `query` module of generic free functions over `NumericIndexRead<T>`.
`ReadOnlyNumericIndexInner` implements `PayloadFieldIndexRead` by plugging
into those helpers; `ReadOnlyNumericIndex` delegates to its inner.

The writable `NumericIndexInner` path is left untouched — its existing
variant-specialized `estimate_points` heuristic stays in `storage`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: make query.rs the single source of truth for numeric index queries

The generic `query` helpers and the per-`NumericIndexInner` impls in
`storage/{trait_impls,statistics}.rs` had duplicated cardinality /
filter / payload-block / condition-checker logic. Collapse them onto
the shared `query` helpers:

- `storage/trait_impls.rs`: `PayloadFieldIndexRead for NumericIndexInner`
  now forwards each method to `query::*` instead of carrying its own copy.
- `storage/statistics.rs`: deleted — `range_cardinality` and
  `estimate_points` were duplicates of the `query` versions.
- `estimate_points` needs a range size; add `values_range_size` to the
  `NumericIndexRead` trait with a default that counts `values_range`,
  overridden by the `Immutable` / `Mmap` variants with their `O(log n)`
  boundary search. The `MutableNumericIndex::map()` accessor (its only
  caller was the old `estimate_points`) is removed.
- `values_range_size` takes `hw_counter` and threads it into
  `values_range` rather than fabricating a disposable counter.

`tests.rs` calls `query::range_cardinality` directly now that the
inherent method is gone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: integrate read-only numeric index into ReadOnlyFieldIndex

Wire the four numeric variants (`IntIndex`, `DatetimeIndex`,
`FloatIndex`, `UuidIndex`) into `ReadOnlyFieldIndex`, mirroring
`FieldIndex`:

- `PayloadFieldIndexRead` / `FieldIndexRead` dispatch covers the new
  variants — telemetry, value counts, value retrievers, `as_numeric`.
- `ReadOnlyNumericFieldIndex` is the read-only counterpart of
  `NumericFieldIndex` (Int/Float order-by erasure over
  `ReadOnlyNumericIndexInner`); `as_numeric` returns it for the
  Int/Datetime/Float variants (UUIDs aren't numerically order-by-able,
  matching `FieldIndex`).
- `ReadOnlyNumericIndex` gains per-`(T, P)` `value_retriever` methods
  (in `read_only/value_retriever.rs`) and an `inner()` accessor.

`StreamRange` is now backed by a shared generic `query::stream_range`
helper over `NumericIndexRead`, implemented for both `NumericIndexInner`
and `ReadOnlyNumericIndexInner` — replacing the bespoke `EitherVariant`
dispatch in `storage/trait_impls.rs`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: collapse ReadOnlyNumericIndex value retrievers onto one generic method

The four per-`(T, P)` `value_retriever` methods were identical except
for the per-value `T -> Value` conversion. Extract that conversion into
a `NumericValueToJson` trait (one tiny impl per `(T, P)`) and keep a
single generic `value_retriever` that builds the retriever closure once.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fmt

* refactor: dedup NumericFieldIndex / ReadOnlyNumericFieldIndex

The two enums were structurally identical — same `StreamRange`,
`get_ordering_values`, and `NumericFieldIndexRead` bodies — differing
only in the backing storage type. Collapse them onto one generic
`NumericFieldIndexView<'a, I, F>` with a single set of impls (over
`I: NumericIndexRead<i64> + StreamRange<i64>` and the `f64` counterpart).

`NumericFieldIndex` and `ReadOnlyNumericFieldIndex` are now type aliases
of the generic view, so every existing call site is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 19:06:32 +02:00
Daniel Boros
823a06e607 feat: restructure null index (#9042) 2026-05-14 14:23:05 +02:00
Daniel Boros
00195aed22 refactor: read only geoindex (#9033)
* refactor: read only geo index

* fix: linter

* fix: drop open temp

* fix: read_ops

* fix: address review fixes and use same file structure like map index

* fix: review comments

* fix: linter
2026-05-14 14:12:15 +02:00
Jojii
9f76cb20ab Determinism for TQ Bits2 HNSW tests (#9036) 2026-05-14 11:07:51 +02:00
Roman Titov
3ed305711a Refactor quantized multi-vector scorers for io_uring support (#8988)
* Only read each vector once, when scoring quantized multi-vectors

* Simplify `QuantizedMultiQueryScorer`

Replace generic `TEncodedVectors` with concrete `QuantizedMultivectorStorage`

* Simplify `QuantizedMultiCustomQueryScorer`

Replace generic `TEncodedVectors` with concrete `QuantizedMultivectorStorage`

* Further simplify `QuantizedMultiCustomQueryScorer`

Remove `TElement` and `TMetric` type-parameters

* Add `iter_offsets` method to quantized multi-vector storage types

* Add `QuantizedMultivectorStorage::score_multi` method...

...which accepts `MultivectorOffset` instead of `PointOffsetType`

* Implement `QuantizedMultiQueryScorer::score_stored_batch`

* Implement `QuantizedMultiCustomQueryScorer::score_stored_batch`
2026-05-13 15:48:32 +02:00
Daniel Boros
c80337c55b feat: add readonly bool (#9030) 2026-05-13 14:39:29 +02:00
Jojii
2861c6118c Fix flaky oversampling assertion in HNSW quantization tests (#9029) 2026-05-13 12:55:03 +02:00
Daniel Boros
ee487d058b refactor: read only boolindex (#9023)
* feat: add read only boolean index

* fix: linter

* chore: merge BoolIndex impl
2026-05-13 12:45:02 +02:00
Andrey Vasnetsov
5551853b36 read only map index (#9025)
* rename mmap -> universal

* [AI] Introduce `ReadOnlyMapIndex` and implement `MapIndexRead` for it

* add missing file

* [AI] Implement read-only PayloadFieldIndexRead / FieldIndexRead (#9026)

Wires `ReadOnlyFieldIndex` into the read-only field-index surface that
`ReadOnlyMapIndex` and `ReadOnlyNullIndex` already provide, sharing the
non-trivial bodies with the existing `MapIndex` impls.

- Extracted per-`N` `PayloadFieldIndexRead` bodies (filter,
  estimate_cardinality, for_each_payload_block, condition_checker) in
  `payload_index_impl/{str,int,uuid}.rs` into free functions over
  `T: MapIndexRead<N>`. Both `MapIndex<N>` and `ReadOnlyMapIndex<N, S>`
  now impl `PayloadFieldIndexRead` via thin delegation to those bodies.
- Lifted `value_retriever` bodies in `value_indexer_impl.rs` the same
  way; `ReadOnlyMapIndex<N, S>` gets the per-K closure for free.
- Added `telemetry_index_type` (required) + `get_telemetry_data`
  (default) to `MapIndexRead`, mirroring the `NullIndexRead` pattern;
  every map-index variant now reports telemetry without an inherent
  method on the enum.
- Promoted `MapIndexRead` + its module to `pub` so
  `field_index_base/read_only/` can name them.
- Implemented `FacetIndex for ReadOnlyMapIndex<N, S>` and extended
  `FacetIndexEnum` with `S: UniversalRead = MmapFile` plus the three
  read-only variants. `FieldIndex::as_facet_index` pins
  `S = MmapFile` via turbofish to keep inference happy.
- `ReadOnlyFieldIndex::as_numeric` returns `None` for now; no concrete
  read-only numeric type exists yet (the doc on `NumericFieldIndexRead`
  already notes this is intended for a future `ReadOnlySegment`).
2026-05-13 12:09:04 +02:00
xzfc
4bd0cb29f5 Replace Meta with U: UserData (#9009) 2026-05-13 00:06:27 +00:00
qdrant-cloud-bot
3fca28cfbd refactor: split geo_index/mod.rs into separate files (#9018)
The geo_index module was a single 2134-line file. Split it into focused
files for better readability and maintainability:

- mod.rs: GeoMapIndex enum definition and core methods (~290 lines)
- builders.rs: GeoMapIndexMmapBuilder and GeoMapIndexGridstoreBuilder
- payload_index.rs: ValueIndexer, PayloadFieldIndex, PayloadFieldIndexRead impls
- tests.rs: all test code (~1340 lines)

No logic changes; all 40 existing tests pass.

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 20:57:09 +02:00
Andrey Vasnetsov
6500879b65 refactor(map_index): extract inner state; add ReadOnlyAppendableMapIndex skeleton (#9020)
* refactor(map_index): extract MutableMapIndexInner; add ReadOnlyAppendableMapIndex

Extract the four in-memory fields (`map`, `point_to_values`, `indexed_points`,
`values_count`) of `MutableMapIndex<N>` into a shared inner struct
`MutableMapIndexInner<N>` under `mutable_map_index/inner.rs`. The
`MapIndexRead<N>` impl moves wholesale onto the inner. `MutableMapIndex<N>`
becomes a thin wrapper `{ inner, storage: Gridstore<...> }`; its
`MapIndexRead<N>` impl forwards to the inner. The Gridstore load loop in
`open_gridstore` collapses to `MutableMapIndexInner::empty()` +
`inner.ingest(idx, value)` per row, sharing the per-value ingestion path with
the future read-only loader.

Add a new sibling `mutable_map_index/read_only/` module containing
`ReadOnlyAppendableMapIndex<N>` (inner + `GridstoreReader<Vec<...>, MmapFile>`),
with its `MapIndexRead<N>` impl forwarding to the same inner. No constructor
yet — loading and lifecycle for the read-only variant will land in a
follow-up. Field `storage` is held to pin the on-disk type and tagged
`#[allow(dead_code)]` until then.

No behavioural change. The 17 existing map_index tests pass unchanged.

* expose storage generic
2026-05-12 19:02:53 +02:00
Daniel Boros
816bc78eb4 refactor: read only nullindex (#9019)
* feat: add read only null index

* fix: linter

* chore: remove unused impl

* chore: revert impl deletion

* fix: linter errors and rename shared->read_ops

* fix: linter

* review fixes

---------

Co-authored-by: generall <andrey@vasnetsov.com>
2026-05-12 17:38:21 +02:00
Andrey Vasnetsov
7fd671cb79 refactor(map_index): split mod.rs into read_ops, lifecycle, tests (#9015)
* refactor(map_index): split mod.rs into read_ops, lifecycle, tests

mod.rs is now ~40 lines containing only the MapIndex enum, type
aliases, and module declarations.

- read_ops.rs: read-only inherent methods (get_values, get_iterator,
  for_each_*, except_cardinality, except_set, telemetry, ram_usage,
  mutability/storage type)
- lifecycle.rs: open/builder/flush/wipe/remove_point/files/populate/
  clear_cache
- tests.rs: all #[cfg(test)] tests

Also folded payload_index_impl_{int,str,uuid}.rs into a dedicated
payload_index_impl/ submodule.

* refactor(map_index): split storage submodules into dedicated dirs (#9016)

* refactor(map_index): split storage submodules into dedicated module dirs

Turn each of the three storage implementation files into a directory
module split into read_ops + lifecycle, mirroring the parent module
layout introduced in #9015.

- mutable_map_index/{mod,lifecycle,read_ops}.rs
- immutable_map_index/{mod,lifecycle,read_ops}.rs
- mmap_map_index/{mod,lifecycle,read_ops}.rs

Each mod.rs holds only the struct definitions, internal Storage type,
and config/constants. read_ops.rs holds the read-only query methods;
lifecycle.rs holds open/build/flush/wipe/files/remove_point and
internal mutation helpers.

Pure refactor — no behavior change.

* refactor(map_index): introduce MapIndexRead trait

Define a unified read-only trait `MapIndexRead<N>` describing the
methods every storage variant exposes (check_values_any, get_values,
get_iterator, for_each_*, storage_type, ram_usage_bytes, etc.).

Each storage variant's read_ops.rs now contains a trait impl instead
of inherent methods. Signatures are unified across variants:

- `hw_counter` is accepted by every method that needs it for the mmap
  variant; mutable / immutable accept and ignore it.
- `check_values_any` returns `bool` (mmap absorbs IO errors internally
  with the existing FIXME, matching the parent's prior `.unwrap_or`).
- `for_each_count_per_value` takes `deferred_internal_id` uniformly;
  the immutable variant `debug_assert!`s it is `None`.

`for_points_values` keeps its variant-specific callback signatures
and stays as an inherent method — it's only used by FacetIndex with
explicit pattern matching.

Pure refactor — no behavior change.

* refactor(mutable_map_index): drop single-variant Storage enum

The `Storage<T>` enum had only one variant (`Gridstore`), so every
`match &self.storage { Storage::Gridstore(s) => ... }` was just
unwrapping the same path. Replace the field with `Gridstore<Vec<...>>`
directly and inline every match.
2026-05-12 16:21:37 +02:00
qdrant-cloud-bot
7547e16b78 refactor(map_index): split monolithic mod.rs into focused submodules (#8999)
The ~2150-line map_index/mod.rs was difficult to navigate. Split it into
focused files while preserving all logic and public API:

- key.rs: MapIndexKey trait and impls for str, IntPayloadType, UuidIntType
- builders.rs: MapIndexBuilder, MapIndexMmapBuilder, MapIndexGridstoreBuilder
- payload_index_impl_str.rs: PayloadFieldIndex/Read for MapIndex<str>
- payload_index_impl_int.rs: PayloadFieldIndex/Read for MapIndex<IntPayloadType>
- payload_index_impl_uuid.rs: PayloadFieldIndex/Read for MapIndex<UuidIntType>
- facet_index_impl.rs: FacetIndex impl for MapIndex<N>
- value_indexer_impl.rs: ValueIndexer impls and value_retriever methods

mod.rs retains the MapIndex enum, its core inherent methods, type aliases,
constants, and tests.

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 13:10:02 +02:00
Andrey Vasnetsov
295e65e960 refactor(field_index): make FieldIndexRead a supertrait of PayloadFieldIndexRead (#8996)
* refactor(field_index): make FieldIndexRead a supertrait of PayloadFieldIndexRead

Removes the `get_payload_field_index_read() -> &dyn PayloadFieldIndexRead`
bridge from `FieldIndexRead` and the five default impls that forwarded
through it. The overlapping read methods now come from the supertrait
directly, eliminating one layer of dynamic dispatch on the hot read path:
`FieldIndex::filter` → variant match → concrete typed-index method.

`FieldIndex` gains a direct `impl PayloadFieldIndexRead` block with
per-method match arms, mirroring the existing dispatch shape used by
`get_telemetry_data`, `values_count`, etc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: apply nightly rustfmt

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(numeric_index): split mod.rs into focused submodules (#8997)

* refactor(numeric_index): split mod.rs into focused submodules

`numeric_index/mod.rs` was 1308 lines mixing the storage-dispatch enum,
the public `NumericIndex<T, P>` wrapper, three builders, per-(T, P)
`ValueIndexer` impls, and the `Encodable`/`StreamRange` traits — hard
to navigate.

Split into:
- `mod.rs` keeps the shared traits (`Encodable`, `StreamRange`),
  `Range<T>::as_index_key_bounds`, and re-exports.
- `wrapper.rs` — `NumericIndex<T, P>` + inherent impl + `NumericIndexIntoInnerValue` trait.
- `builders.rs` — `NumericIndexBuilder`, `NumericIndexMmapBuilder`, `NumericIndexGridstoreBuilder`.
- `value_indexer.rs` — `ValueIndexer` and per-(T, P) `value_retriever` inherent impls.
- `storage/` — the `NumericIndexInner` dispatch enum:
  - `storage/mod.rs` — enum + simple match-and-forward (constructors, lifecycle, telemetry, per-point access).
  - `storage/statistics.rs` — histogram-driven cardinality and point-count helpers.
  - `storage/trait_impls.rs` — `PayloadFieldIndex`, `PayloadFieldIndexRead`, `StreamRange` impls.

Pure code reorganization — no behavior change. A few inherent methods
on `NumericIndexInner` had to widen from private to `pub(super)` /
`pub(in crate::index::field_index::numeric_index)` to remain reachable
across the new module boundaries (and from `tests.rs`); the new
constructors (`NumericIndexMmapBuilder::new`,
`NumericIndexGridstoreBuilder::new`) replace direct field
construction across files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(numeric_index): rename wrapper.rs -> numeric_index.rs, move point_ids_by_value out of statistics

- Renamed `wrapper.rs` to `numeric_index.rs`, matching the central
  `NumericIndex` type and the surrounding module name.
- Moved `point_ids_by_value` from `storage/statistics.rs` to
  `storage/mod.rs` next to `get_values`. It is an exact value->points
  lookup primitive, not a cardinality estimate; the statistics module
  is left to the histogram-driven helpers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(clippy): rename numeric_index submodule to index to fix module_inception lint

Clippy's module_inception rule disallows a module with the same name as
its containing module. Rename numeric_index.rs -> index.rs and update
the three internal references.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(field_index): push PayloadFieldIndexRead through NumericIndex and move special_check_condition per-variant (#8998)

Two related cleanups that share a theme — drop enum-level read dispatch
in favor of per-variant trait impls:

1. `NumericIndex<T, P>` now implements `PayloadFieldIndexRead` directly
   (forwarding to its inner storage enum). The four numeric arms in
   `FieldIndex`'s `impl PayloadFieldIndexRead` drop their `.inner()`
   calls, so all eleven variants now use a uniform `idx.<method>(...)`
   form.

2. `special_check_condition` moves from `FieldIndexRead` to
   `PayloadFieldIndexRead` with a default `Ok(None)` body.
   `FullTextIndex` (the only variant with non-trivial logic) overrides
   it. `NumericIndex<T, P>` forwards through to the inner enum, and
   the `FieldIndex` enum's match dispatch moves out of `FieldIndexRead`
   into `PayloadFieldIndexRead` for consistency with the other five
   trait methods. Removed the now-redundant declaration from
   `FieldIndexRead` (inherited via the supertrait).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 13:09:19 +02:00
xzfc
1da4ab7d6d Integrate UniversalHashMap (#8954)
* Callback-based InvertedIndex::for_each_vocab_with_postings_len

* Integrate UniversalHashMap into full-text index

* Integrate UniversalHashMap into immutable_map_index
2026-05-12 04:31:33 +00:00
Andrey Vasnetsov
500c7cca16 refactor(field_index): extract FieldIndexRead impl into separate file (#8995)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 01:00:30 +02:00
Andrey Vasnetsov
bebe222d60 refactor(read_view): make StructPayloadIndexReadView generic over F: FieldIndexRead (#8994)
The read view's `field_indexes` was concretely typed as
`&IndexesMap = &HashMap<PayloadKeyType, Vec<FieldIndex>>`. Replace
the FieldIndex with a generic parameter F: FieldIndexRead, so the
view advertises a dependency on the trait surface rather than the
concrete enum. `StructPayloadIndex::with_view` instantiates
`F = FieldIndex`; any future consumer that implements
FieldIndexRead (e.g. a read-only segment with a narrower
field-index representation) can plug in directly.

Changes:

1. StructPayloadIndexReadView gains a fourth generic parameter F
   bound by FieldIndexRead. `field_indexes` is now
   `&HashMap<PayloadKeyType, Vec<F>>`. All five impl blocks across
   read_view/ propagate the F generic.

2. `check_field_condition` / `select_nested_indexes` /
   `check_payload` in payload_storage/query_checker.rs become
   generic over `FI: FieldIndexRead` (and `R: AsRef<Vec<FI>>`).
   The bodies only call `special_check_condition` which is on
   FieldIndexRead, so the generalization is free.

3. `variable_retriever` in read_view/value_retriever/helpers.rs
   gains an F generic; the body already only uses FieldIndexRead
   methods after the previous commits in this PR.

4. `with_view` and `SegmentReadViewFor` instantiate F = FieldIndex.
   No external consumer breaks.

5. Tests construct the view with an inferred F.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 00:15:31 +02:00
generall
d914c0edac Revert "refactor(read_view): make StructPayloadIndexReadView generic over F: FieldIndexRead"
This reverts commit 3ef647991d.
2026-05-11 23:40:21 +02:00
generall
3ef647991d refactor(read_view): make StructPayloadIndexReadView generic over F: FieldIndexRead
The read view's `field_indexes` was concretely typed as
`&IndexesMap = &HashMap<PayloadKeyType, Vec<FieldIndex>>`. Replace
the FieldIndex with a generic parameter F: FieldIndexRead, so the
view advertises a dependency on the trait surface rather than the
concrete enum. `StructPayloadIndex::with_view` instantiates
`F = FieldIndex`; any future consumer that implements
FieldIndexRead (e.g. a read-only segment with a narrower
field-index representation) can plug in directly.

Changes:

1. StructPayloadIndexReadView gains a fourth generic parameter F
   bound by FieldIndexRead. `field_indexes` is now
   `&HashMap<PayloadKeyType, Vec<F>>`. All five impl blocks across
   read_view/ propagate the F generic.

2. `check_field_condition` / `select_nested_indexes` /
   `check_payload` in payload_storage/query_checker.rs become
   generic over `FI: FieldIndexRead` (and `R: AsRef<Vec<FI>>`).
   The bodies only call `special_check_condition` which is on
   FieldIndexRead, so the generalization is free.

3. `variable_retriever` in read_view/value_retriever/helpers.rs
   gains an F generic; the body already only uses FieldIndexRead
   methods after the previous commits in this PR.

4. `with_view` and `SegmentReadViewFor` instantiate F = FieldIndex.
   No external consumer breaks.

5. Tests construct the view with an inferred F.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 23:36:22 +02:00
Andrey Vasnetsov
c88b4323a1 refactor(field_index): add condition_checker trait method (foundation) (#8978)
* refactor(field_index): add condition_checker trait method (foundation)

First PR in the condition-checker-migration sub-plan
(docs/plans/field-index-read-trait/condition-checker-migration/).
Purely additive: every existing typed index keeps the default `None`
impl, so the legacy match in `field_condition_index` handles every
condition. Behaviour unchanged.

Changes:

1. PayloadFieldIndexRead::condition_checker — new method with default
   `None` body. Lets each typed index advertise the conditions it can
   serve.

2. FieldIndexRead::get_payload_field_index_read — new required method
   returning `&dyn PayloadFieldIndexRead`. Distinct from the existing
   inherent `get_payload_field_index` (which returns `&dyn PayloadFieldIndex`,
   exposing write-side methods) — the read path now hands back a
   handle that can't accidentally call wipe / flusher / files.

3. Five pure-delegation methods on FieldIndexRead get default impls
   using the new getter (count_indexed_points, filter,
   estimate_cardinality, for_each_payload_block, condition_checker).
   `impl FieldIndexRead for FieldIndex` drops their explicit bodies.

4. field_condition_index in read_view/condition_converter/helpers.rs
   tries `index.condition_checker(...)` first, then falls through to
   the existing legacy match. The match handles everything until
   per-variant overrides land in the follow-up PRs.

Why `&dyn` rather than `impl PayloadFieldIndexRead + '_`: RPITIT
requires a single concrete return type, but the per-variant arms
produce `&NumericIndexInner<T>`, `&MapIndex<K>`, `&GeoMapIndex`, etc.
— different types. A unifying borrowed-enum (à la FacetIndexEnum)
would be more code than the trait-object indirection saves.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(field_index): migrate GeoMapIndex into condition_checker (#8979)

* refactor(field_index): migrate GeoMapIndex condition checking into condition_checker

Sub-plan PR 1 of the condition-checker-migration. GeoMapIndex now
serves geo conditions (geo_radius, geo_bounding_box, geo_polygon)
directly through PayloadFieldIndexRead::condition_checker. The
corresponding match arms and three helper functions in the read
view's helpers.rs are gone — the polymorphic dispatch in
field_condition_index handles them.

Changes:

1. impl PayloadFieldIndexRead for GeoMapIndex gains a condition_checker
   override. Bodies copied from get_geo_radius_checkers /
   get_geo_bounding_box_checkers / get_geo_polygon_checkers; the
   `&'a self` capture replaces the &FieldIndex match arm.

2. The override destructures FieldCondition explicitly (no `..`,
   every field named with either a binding or `_:`). This forces a
   compile error if a new field is added to FieldCondition, so each
   index variant must explicitly decide whether to handle it. Same
   pattern is mandated for the remaining variant migrations — see
   docs/plans/field-index-read-trait/condition-checker-migration/00-overview.md.

3. read_view/condition_converter/helpers.rs drops the three Geo
   FieldCondition arms from field_condition_index's legacy match
   and deletes the three helper functions. The catch-all is relaxed
   to `geo_radius: _ | geo_bounding_box: _ | geo_polygon: _` since
   the trait method handles all geo cases now.

No behavioural change: non-Geo variants continue to return None for
geo conditions (the trait default returns None), and the read view's
catch-all still falls back to the payload check.

Stacked on #8978 (condition-checker-trait foundation).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(field_index): migrate NullIndex into condition_checker (#8980)

* refactor(field_index): migrate NullIndex condition checking into condition_checker

Sub-plan PR 2 of the condition-checker-migration. NullIndex now
serves is_empty / is_null conditions directly through
PayloadFieldIndexRead::condition_checker. The dispatch in the read
view's condition_converter/mod.rs collapses to mirror the
Condition::Field arm: try every index for the field, fall back to
the payload check on unwrap_or_else.

Changes:

1. impl PayloadFieldIndexRead for NullIndex gains a condition_checker
   override. Explicit FieldCondition destructure (no `..`) — binds
   is_empty and is_null, every other field uses `_:` to document
   intentional non-handling.

2. read_view/condition_converter/helpers.rs drops the is_empty /
   is_null arms from field_condition_index's legacy match and deletes
   five orphaned helpers: get_is_empty_checker, get_is_null_checker,
   get_is_empty_indexes, get_null_index_is_empty_checker,
   get_fallback_is_empty_checker.

3. read_view/condition_converter/mod.rs rewrites Condition::IsEmpty
   and Condition::IsNull to use field_condition_index with a
   constructed FieldCondition, same shape as Condition::Field.

Behavioural note (decision locked during review): The legacy
values_is_empty fast-path for "field has indexes but no NullIndex"
is dropped. NullIndex was introduced in v1.13.5 (#6088); we're on
v1.18.0, so any collection touched in the past ~5 minor releases has
a NullIndex. Segments untouched since pre-v1.13.5 fall through to the
payload check on is_empty queries — no longer accelerated by another
index's values_is_empty. A re-index recovers the original perf.

Stacked on #8979 (GeoMapIndex) → #8978 (foundation) → dev.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(field_index): migrate NumericIndex into condition_checker (#8981)

* refactor(field_index): migrate NumericIndex condition checking into condition_checker

Sub-plan PR 3 of the condition-checker-migration. NumericIndexInner<T>
now serves Range conditions directly through
PayloadFieldIndexRead::condition_checker. The generic impl handles all
four FieldIndex variants (IntIndex, FloatIndex, DatetimeIndex,
UuidIndex) with one trait override.

Changes:

1. impl PayloadFieldIndexRead for NumericIndexInner<T> gains a
   condition_checker override. Explicit FieldCondition destructure
   (no `..`) binds `range`. Body converts the RangeInterface bounds
   into T via T::from_f64 / T::from_u128 — same conversion `filter`
   already uses.

2. read_view/condition_converter/helpers.rs drops the range arm from
   field_condition_index's legacy match and deletes
   get_range_checkers / get_float_range_checkers /
   get_datetime_range_checkers (3 helpers, ~75 lines). Six unused
   imports go with them.

Behavioural note: The legacy helpers were stricter than `filter` —
only IntIndex/FloatIndex served Float ranges, only DatetimeIndex
served DateTime ranges; UuidIndex returned None. `filter` already
handles every numeric variant uniformly via T::from_f64 / T::from_u128,
so there was a pre-existing inconsistency between filter (primary-
clause iteration) and condition_checker (post-filter validation).
This PR makes condition_checker match filter — every NumericIndex
variant can now serve any RangeInterface. The schema layer normally
prevents cross-type range queries from reaching here.

Stacked on #8980 (NullIndex) → #8979 (GeoMapIndex) → #8978
(foundation) → dev.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(field_index): migrate MapIndex into condition_checker (#8982)

* refactor(field_index): migrate MapIndex condition checking into condition_checker

Sub-plan PR 4 of the condition-checker-migration. MapIndex<K> now
serves Match::Value / Match::Any / Match::Except conditions directly
through PayloadFieldIndexRead::condition_checker, one override per K
(MapIndex<str>, MapIndex<UuidIntType>, MapIndex<IntPayloadType>).

Changes:

1. Three impl blocks gain condition_checker overrides — one per K.
   Each uses an explicit FieldCondition destructure (no `..`) and an
   exhaustive `Match` variant match (no `_ => None`); a new variant
   in FieldCondition, Match, ValueVariants, or AnyVariants forces a
   compile error in every relevant place. INDEXSET_ITER_THRESHOLD
   small/large branching preserved.

2. read_view/condition_converter/match_converter.rs sheds the Map
   arms. get_match_value_checker keeps (Bool, BoolIndex) plus the
   explicit 30-tuple None list. get_match_any_checker becomes an
   explicit 22-tuple None match (no Bool/FullText/Numeric/Geo/Null
   variant ever served Any). get_match_except_checker reduces to the
   unconditional values_count > 0 fallback — when no typed index
   handles Except (via condition_checker), match any point with at
   least one value, since the value can't be in a type-mismatched
   list. Unused IndexSet/Uuid/INDEXSET_ITER_THRESHOLD imports drop.

Stacked on #8981 (NumericIndex) → #8980 (NullIndex) → #8979
(GeoMapIndex) → #8978 (foundation) → dev.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(field_index): migrate BoolIndex into condition_checker (#8983)

* refactor(field_index): migrate BoolIndex condition checking into condition_checker

Sub-plan PR 5 of the condition-checker-migration. BoolIndex now
serves `Match::Value(Bool)` directly through
PayloadFieldIndexRead::condition_checker.

Changes:

1. impl PayloadFieldIndexRead for BoolIndex gains a condition_checker
   override. Explicit FieldCondition destructure (no `..`) plus an
   exhaustive `Match` variant match (no `_ => None`). Match::Any /
   Match::Except over booleans return None — matches legacy
   match_converter behaviour.

2. read_view/condition_converter/match_converter.rs's
   get_match_value_checker drops the `(Bool, BoolIndex)` arm; the
   function now returns None unconditionally for every (value, index)
   pair, with the explicit 33-tuple list preserved so adding a new
   ValueVariants or FieldIndex variant forces a compile error.

Stacked on #8982 (MapIndex) → #8981#8980#8979#8978 → dev.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(field_index): migrate FullTextIndex into condition_checker (#8984)

* refactor(field_index): migrate FullTextIndex condition checking into condition_checker

Sub-plan PR 6 of the condition-checker-migration. FullTextIndex now
serves Match::Text / Match::TextAny / Match::Phrase directly through
PayloadFieldIndexRead::condition_checker.

Changes:

1. impl PayloadFieldIndexRead for FullTextIndex gains a
   condition_checker override. Explicit FieldCondition destructure
   (no `..`) plus exhaustive Match variant matching (no `_ => None`).
   Match::Value / Match::Any / Match::Except all return None
   explicitly. Body copies parse_text_query / parse_phrase_query /
   parse_text_any_query dispatch and the check_match closure from the
   legacy helper, preserving the FIXME-marked error swallowing.

2. read_view/condition_converter/match_converter.rs loses
   get_match_text_checker and the TextQueryType enum (both
   unused after this PR). get_match_checkers' Text/TextAny/Phrase
   arms collapse to a single explicit `None` arm.

Stacked on #8983 (BoolIndex) → #8982 (MapIndex) → #8981#8980 →

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(field_index): remove legacy condition_checker dispatch helpers (#8985)

* refactor(field_index): remove legacy condition_checker dispatch helpers

Final PR (7) of the condition-checker-migration sub-plan. With every
typed FieldIndex variant now serving its conditions via
PayloadFieldIndexRead::condition_checker (PRs 1-6), the wrapper
`field_condition_index` and the legacy match dispatch are no longer
needed.

Changes:

1. read_view/condition_converter/mod.rs's three call sites
   (Condition::Field, Condition::IsEmpty, Condition::IsNull) call
   `index.condition_checker(...)` directly instead of going through
   the `field_condition_index` wrapper.

2. helpers.rs (49 lines) and match_converter.rs (119 lines) deleted —
   `field_condition_index`, `get_match_checkers`,
   `get_match_value_checker`, `get_match_any_checker`,
   `get_match_except_checker` are all gone.

3. The directory `condition_converter/` collapses to a flat file
   `condition_converter.rs` (mod.rs renamed via git).

Behavioural note: the legacy `Match::Except` `values_count > 0`
fallback for type-mismatched Except queries is no longer applied —
those now fall through to the payload check via
`check_field_condition`. Schema-level type checking prevents
cross-type Match::Except queries from reaching here in practice, so
the fallback was only useful for degenerate inputs; payload check is
semantically equivalent.

Audit grep on `lib/segment/src/`:

  $ grep -rn "field_condition_index\|get_match_checkers\|...\|TextQueryType"
  (no matches)

All legacy helper machinery is gone.

Stacked on #8984 (FullTextIndex) → #8983#8982#8981#8980 →

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(field_index): migrate indexed_variable_retriever into FieldIndexRead (#8986)

* refactor(field_index): migrate indexed_variable_retriever into FieldIndexRead

Follow-up to the condition-checker-migration sub-plan. The read view's
`value_retriever/helpers.rs` previously destructured `FieldIndex`
variants inside `indexed_variable_retriever` to build per-point value
extractors. This PR turns that into a `value_retriever` trait method
on `FieldIndexRead`, so the helper now depends only on the trait —
no variant destructuring outside `FieldIndex`'s own impl.

Changes:

1. FieldIndexRead gains `value_retriever(&self, hw_counter)`. Lives
   on the enum-level trait (rather than `PayloadFieldIndexRead`)
   because the value→Value conversion depends on the payload type
   `U` in `NumericIndex<T, U>`, and only `FieldIndex` knows the
   `(T, U)` pairing.

2. impl FieldIndexRead for FieldIndex implements value_retriever
   with an 11-arm variant match. Bodies copied verbatim from the old
   `indexed_variable_retriever`. FullTextIndex and NullIndex return
   None (caller falls back to payload).

3. read_view/value_retriever/helpers.rs:
   - `variable_retriever` now calls `index.value_retriever(hw_counter)`
     directly via trait dispatch
   - `indexed_variable_retriever` deleted
   - Unused imports dropped (Number, DateTimePayloadType,
     UuidPayloadType, MultiValue)

Helpers.rs shrinks from 189 lines to 73 lines (the test module
stays). Total: +134 / -122 across 3 files, but the structural change
is moving the dispatch into the trait.

Stacked on #8985 (condition-checker-cleanup) → #8984 → … → #8978 → dev.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(field_index): push value_retriever conversion logic into typed indexes

`FieldIndex::value_retriever` previously inlined the per-variant
value→JSON `Value` conversion (Number, String, DateTime, UUID, Bool,
serde_json::to_value for geo) as a 100-line variant match. Move each
variant's conversion into an inherent `value_retriever` method on the
corresponding typed index; `FieldIndex` is left with a thin
11-arm dispatch that just delegates.

Changes:

1. NumericIndex<T, U> gains 4 per-(T, U) inherent value_retriever
   impls (Int/Datetime/Float/Uuid). The conversion is U-specific
   (timestamp formatting, UUID string, Number::from_f64 etc.), so
   each (T, U) pair has its own impl block.

2. MapIndex<K> gains 3 per-K inherent value_retriever impls
   (Keyword/Int/Uuid).

3. GeoMapIndex and BoolIndex each gain a single inherent
   value_retriever.

4. FieldIndex::value_retriever collapses to a 11-arm match that
   delegates to the typed index's inherent method. FullTextIndex
   and NullIndex still return None at the FieldIndex level — they
   don't expose value retrieval and the caller falls back to payload.

5. field_index.rs sheds the Number, MultiValue and Box::new
   noise — about 100 lines smaller.

Same shape as how `wipe`, `add_point`, `get_telemetry_data` already
delegate from FieldIndex to typed indexes. Adding a new FieldIndex
variant now forces compile errors in (a) the dispatch in FieldIndex
and (b) the new typed index's inherent impl — both correct places.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(field_index): push BoolIndex/NullIndex condition_checker into storage variants

The `BoolIndex` and `NullIndex` wrappers were special-cased among
typed indexes: their `PayloadFieldIndexRead::condition_checker`
impls did the variant-specific work directly on `self` (matching
`Match::Value(Bool)`, `is_empty`, `is_null`) and called
`self.check_values_any` / `self.values_is_empty` / `self.values_is_null`,
each of which dispatched `match self { Mmap(...) | Immutable(...) }`
per point. The inner storage variants (`Immutable/MutableBoolIndex`,
`Immutable/MutableNullIndex`) implemented `PayloadFieldIndexRead`
solely for `filter` / `count_indexed_points` / `estimate_cardinality`
/ `for_each_payload_block` — their `condition_checker` was unreachable
behind the trait's default `None` body.

Removing the trait default surfaced this asymmetry. Rather than stub
the inner-type `condition_checker` as dead `None`, push the real
logic down to where the storage variants live — same shape as
`filter` already uses:

  inner: explicit FieldCondition destructure + exhaustive Match
         arms; per-point closure captures `&'a self` of the inner
         type directly.
  outer: 2-arm `match self { ... => inner.condition_checker(...) }`.

Result: per-point closure body no longer hits a `match self { ... }`
on the wrapper. The wrapper dispatch happens once during setup when
the wrapper picks which inner closure to return. Matches the pattern
the other 5 trait methods (filter/count/estimate/for_each_block) on
these wrappers already use.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* CI fix

* upd docstring

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 21:10:26 +02:00
Daniel Boros
b766d4424f feat/vector-index-read-only (#8891)
* feat: add vector index read only

* fix: linter

* chore: trigger ci

* feat: add blank impls

* refactor: split hnsw (#8991)

* feat: split hnsw

* refactor: split sparse vector index (#8992)

* refactor: split hnsw

* fix: linter

* refactor: split sparse vector index
2026-05-11 18:14:37 +02:00
Andrey Vasnetsov
4a7643905e refactor: relocate read-view-exclusive helpers under read_view/ (#8977)
The helpers in query_optimization/condition_converter{,/match_converter}.rs
and the bulk of query_optimization/rescore_formula/value_retriever.rs were
only ever called from struct_payload_index/read_view/. Move them next to
their callers.

The trait split work (#8975, #8976) showed why these helpers can't migrate
to a uniform FieldIndexRead bound: every branch in field_condition_index,
match_converter, get_range_*, get_geo_*, indexed_variable_retriever
destructures FieldIndex variants and reaches into variant-specific
predicates on the underlying typed indexes (MapIndex::check_values_any,
FullTextIndex::parse_*_query, NumericIndex::get_values, …). The variant
match is the whole point of these helpers; co-locating them with the read
view is the right shape.

New layout:
- read_view/condition_converter/{mod.rs, helpers.rs, match_converter.rs}
  (was a flat read_view/condition_converter.rs + scattered query_optimization
  files)
- read_view/value_retriever/{mod.rs, helpers.rs}
  (was a flat read_view/value_retriever.rs + query_optimization/rescore_formula/
  value_retriever.rs)

VariableRetrieverFn type alias stays in query_optimization/rescore_formula/
value_retriever.rs (now a 6-line file): formula_scorer.rs in the same module
uses it as a struct field, so the type belongs there. Only the helper
functions and their tests move.

Visibility tightened where natural: cross-file entry points are
pub(super), purely-local helpers are private, public-API surface is
unchanged.

No behavioural change. cargo test -p segment: 666 unit + 120 integration
tests pass.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 16:49:53 +02:00
Andrey Vasnetsov
b91c85776a refactor: implement FieldIndexRead for FieldIndex (#8976)
* refactor: implement FieldIndexRead for FieldIndex

Move the 10 read-only inherent methods on FieldIndex (special_check_condition,
count_indexed_points, filter, estimate_cardinality, for_each_payload_block,
get_telemetry_data, values_count, values_is_empty, as_numeric, as_facet_index)
into impl FieldIndexRead for FieldIndex. Bodies are unchanged.

Write/lifecycle methods (add_point, remove_point, wipe, flusher, files,
immutable_files, ram_usage_bytes, is_on_disk, populate, clear_cache,
get_full_index_type) stay inherent on FieldIndex.

Call sites that hold &FieldIndex now resolve through the trait — added
`use ... FieldIndexRead;` imports where the compiler asked
(read_view/{payload_index_read,filtering}.rs, query_optimization/
condition_converter.rs and match_converter.rs, payload_storage/
query_checker.rs, two integration tests, full_text_index tests module).

StructPayloadIndex::get_facet_index keeps its concrete
OperationResult<FacetIndexEnum<'_>> return: the trait method as_facet_index
returns Option<impl FacetIndex + '_> (RPITIT) which can't be downcast back
to FacetIndexEnum, so the function inlines the variant match. Same shape as
before, just expressed locally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: remove unused StructPayloadIndex::get_facet_index

Confirmed dead code — the function had no callers anywhere in the workspace.
It was preserved across recent refactors (most recently #8967) but was never
referenced. Removing it also drops the now-unused JsonPath and FacetIndexEnum
imports in struct_payload_index/mod.rs.

This obsoletes the inline variant match introduced in the previous commit:
that match existed solely to keep get_facet_index returning the concrete
FacetIndexEnum<'_> when as_facet_index moved to the FieldIndexRead trait
with an opaque return. With get_facet_index gone, no workaround is needed.

The OperationError::MissingMapIndexForFacet variant stays — it is still used
by segment::read_view::facet and lib/collection/src/operations/types.rs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:54:37 +02:00
Andrey Vasnetsov
393f059206 refactor: introduce FieldIndexRead trait (#8975)
Mirror the PayloadFieldIndex / PayloadFieldIndexRead split (#8966) one layer
up at the FieldIndex enum level. FieldIndexRead names the uniform read-only
surface (filter, estimate_cardinality, count_indexed_points,
for_each_payload_block, get_telemetry_data, values_count, values_is_empty,
special_check_condition, as_numeric, as_facet_index) — the methods that
StructPayloadIndexReadView and its helpers reach through IndexesMap.

Trait declaration only. No impl, no callers in this PR — this is purely
additive. Follow-up PRs will impl it on FieldIndex, then migrate
non-destructuring helpers to a FieldIndexRead bound.

as_numeric and as_facet_index use return-position impl Trait
(impl NumericFieldIndexRead + '_, impl FacetIndex + '_), matching how
PayloadIndexRead exposes the same shapes. The trait is therefore not
object-safe; consumers will use a generic bound rather than
&dyn FieldIndexRead.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:56:35 +02:00
Andrey Vasnetsov
8de9c11a5d refactor(index): drop PayloadIndex: PayloadIndexRead super-trait (#8969)
* refactor(index): drop PayloadIndex: PayloadIndexRead super-trait

`PayloadIndex` now only declares the mutating surface; reads live on
the sibling `PayloadIndexRead` trait. The previous super-trait
relationship forced any type that implemented `PayloadIndex` to also
implement `PayloadIndexRead`, blocking a future `PayloadIndexRead`-only
view that doesn't (and shouldn't) own the writable index machinery.

No behavioural change. Audit before committing showed no generic
bound site on `PayloadIndex` exists in the workspace, and every
caller that uses read methods already imports `PayloadIndexRead`
explicitly (the trait was already used as a generic bound on
`SegmentReadView`'s `TPayloadIndex` parameter and on
`iter_filtered_points`). The full workspace builds clean and all
segment / storage / collection tests pass without any consumer
update.

Doc comment on `PayloadIndex` updated to point readers at
`PayloadIndexRead` for the read surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(index): introduce StructPayloadIndexReadView<P, I, V> (#8970)

Move the read surface of `StructPayloadIndex` onto a new borrowed view
struct generic over `<P: PayloadStorageRead, I: IdTrackerRead, V: VectorStorageRead>`.
The view holds exactly the fields that `PayloadIndexRead` requires --
no more, no less:

    pub struct StructPayloadIndexReadView<'a, P, I, V> {
        payload:         &'a Arc<AtomicRefCell<P>>,
        id_tracker:      &'a I,
        vector_storages: &'a HashMap<VectorNameBuf, Arc<AtomicRefCell<V>>>,
        field_indexes:   &'a IndexesMap,
        config:          &'a PayloadConfig,
        visited_pool:    &'a VisitedPool,
    }

`StructPayloadIndex` now exposes a `with_view(|v| ...)` accessor that
borrows `id_tracker` once at the top and constructs the view for the
closure scope. All read-method bodies move onto the view, which is the
sole `PayloadIndexRead` implementor for this index.

Why three generics
==================
- `P: PayloadStorageRead` -- already generic via PR #8968.
- `I: IdTrackerRead` -- direct method calls; held as `&I` (not
  `&Arc<AtomicRefCell<I>>`) because the cell is collapsed at the
  `with_view` boundary, saving a per-method `borrow()` atomic op.
  `dyn IdTrackerRead` does not satisfy `I: IdTrackerRead` bounds in
  Rust without an explicit blanket impl, so generic is the only
  consistent option here.
- `V: VectorStorageRead` -- the only access site is
  `available_vector_count()` for the `HasVector` cardinality branch
  (`condition_cardinality` in `read_view/filtering.rs`).

Why `payload` keeps the `Arc`
=============================
`PayloadProvider<P>::new(...)` (introduced in PR #8968) takes
`Arc<AtomicRefCell<P>>` so that the returned `FormulaScorer<'q>` /
`Box<dyn FilterContext + 'a>` can outlive the caller frame. The view
therefore holds `&'a Arc<AtomicRefCell<P>>` (asymmetric vs the bare
`&I` for `id_tracker`). Switching to a borrow-based provider would
require reworking `formula_scorer` / `filter_context` to callback
style; deferred to a follow-up if needed.

What does NOT move
==================
- `build_field_indexes` and `clear_index_for_point` stay on
  `StructPayloadIndex`. `build_field_indexes` is read-shaped but only
  has write-side callers, and pulls in the `selector` machinery which
  uses `path` + `storage_type`. Keeping it on the writable struct
  means `path` and `is_appendable` do not need to leak into the view.
- The `selector` / `selector_with_type` helpers stay on the writable
  struct for the same reason.
- The free helpers in `query_optimization/condition_converter.rs`
  (range / geo / null / is-empty checkers) stay where they are; their
  visibility is bumped from `fn` to `pub(in crate::index)` so the view
  can still call them.

Module layout
=============
    lib/segment/src/index/struct_payload_index/
        mod.rs                          # owning struct + with_view
        build.rs                        # write-side build coordination
        payload_index.rs                # impl PayloadIndex (mutating only)
        tests.rs
        read_view/
            mod.rs                      # view struct + module wiring
            payload_index_read.rs       # impl PayloadIndexRead for view
            filtering.rs                # struct_filtered_context, condition_cardinality, query_field, estimate_field_condition
            condition_converter.rs      # impl block from query_optimization/
            optimizer.rs                # impl block from query_optimization/
            value_retriever.rs          # impl block from query_optimization/
            tests.rs                    # smoke test that builds the view directly

Consumer migration
==================
- `Segment::with_view` nests the new `StructPayloadIndex::with_view`
  inside it; `SegmentReadViewFor<'s>` uses the view as its
  `TPayloadIndex` parameter.
- HNSW (`hnsw.rs`), sparse (`sparse_vector_index.rs`), plain
  (`plain_vector_index.rs`) call sites wrap their read-method calls in
  `payload_index.borrow().with_view(|v| ...)`.
- `Segment::get_indexed_fields`, `update_all_field_indices`, and
  `SegmentBuilder::build` switch to `with_view` for `indexed_fields()`
  / `get_payload_sequential()`.
- Integration tests and benches similarly migrate.
- `set_payload` (still on `PayloadIndex` write impl) inlines its
  former `self.get_payload(...)` call as `self.payload.borrow().get(...)`
  to avoid going through `with_view` from a `&mut self` write path.

Smoke test (`read_view/tests.rs`) constructs the view directly over
`InMemoryPayloadStorage` + `InMemoryIdTracker` + an empty vector-storage
map, and exercises `indexed_fields()`, `query_points()`, and
`available_point_count()` -- proving the view is genuinely decoupled
from `StructPayloadIndex`. This is the abstraction PR 4 will use to
wire a read-only segment.

Verified
========
- `cargo build --workspace --tests --benches` -- green
- `cargo test -p segment --lib` -- 666 passed (665 + 1 new smoke test), 0 failed
- `cargo test -p segment --tests` -- 120 integration tests pass
- `cargo test -p storage --lib` -- 44 passed
- `cargo test -p collection --lib` -- 197 passed
- `cargo clippy -p segment --tests --benches` -- clean

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 23:39:47 +02:00
Andrey Vasnetsov
d114e42265 refactor(payload_storage): generalize PayloadProvider over PayloadStorageRead (#8968)
Add `payload_ref(point_offset, hw_counter) -> OwnedPayloadRef<'_>` to
the `PayloadStorageRead` trait so payload retrieval no longer needs
to know whether the underlying storage is in-memory (zero-copy
borrow) or mmap/on-disk (materialised clone). In-memory storage
returns `OwnedPayloadRef::Ref(...)` to preserve the existing
zero-copy hot path; on-disk storages return `OwnedPayloadRef::Owned(...)`.

`PayloadProvider` is now generic over `P: PayloadStorageRead`
(`Arc<AtomicRefCell<P>>`) instead of being hard-coded to
`PayloadStorageEnum`. The previous `match`-on-enum dispatch in
`PayloadProvider::with_payload` is gone; dispatch happens through
`PayloadStorageRead::payload_ref`. Manual `Clone` impl avoids
imposing a `P: Clone` bound (cloning the provider is just an `Arc`
bump). The `empty_payload` fallback field is removed -- `payload_ref`
always returns a usable ref.

`<P: PayloadStorageRead + 'a>` is threaded through the consuming
signatures in `query_optimization/`: `optimize_filter`,
`convert_conditions`, `optimize_should/min_should/must/must_not`,
`condition_converter`, `variable_retriever`,
`payload_variable_retriever`. The `+ 'a` bound is required because
the returned `OptimizedFilter<'a>` / `ConditionCheckerFn<'a>` /
`VariableRetrieverFn<'a>` contain boxed closures that capture the
provider. The cascade stops at the box: `StructFilterContext<'a>`,
`FormulaScorer<'a>`, and the rest of the search machinery are
unaffected.

Construction sites (`StructPayloadIndex::formula_scorer`,
`struct_filtered_context`, `retrievers_map`) need no source-level
change -- type inference picks `P = PayloadStorageEnum` from the
`Arc<AtomicRefCell<PayloadStorageEnum>>` they pass in.

The panic-on-corruption policy that lived in the previous Mmap
branch is preserved verbatim and now applies uniformly across
storage backends.

This unblocks building a `PayloadStorageRead`-generic read-only
view of `StructPayloadIndex` (follow-up work).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 20:14:38 +02:00
Andrey Vasnetsov
d91cee72bb refactor split of struct_payload_index.rs (#8967)
* refactor split of struct_payload_index.rs

* review fixes
2026-05-09 18:31:55 +02:00
Andrey Vasnetsov
a85ab22123 refactor: split PayloadFieldIndex into PayloadFieldIndex + PayloadFieldIndexRead (#8966)
Mirror the IdTracker / IdTrackerRead split. PayloadFieldIndexRead holds the
read-only query surface (count_indexed_points, filter, estimate_cardinality,
for_each_payload_block); PayloadFieldIndex: PayloadFieldIndexRead retains
storage-lifecycle methods (wipe, flusher, files, immutable_files).

All 11 impls updated (numeric_index, map_index x3, null_index x3, bool_index
x3, full_text_index, geo_index). Test imports adjusted where they use the
read-side methods.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 17:51:54 +02:00
Andrey Vasnetsov
29b577e036 refactor: split field_index_base.rs into a module (#8965)
Break up the 778-line field_index_base.rs into focused submodules
under field_index_base/ (payload_field_index, value_indexer,
field_index, builder) and relocate NumericFieldIndex /
NumericFieldIndexRead into the numeric_index module where they
naturally belong.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 16:56:03 +02:00
Andrey Vasnetsov
dc1ec94ea0 make storage generic in ReadOnlySparseVectorStorage (#8964)
* make storage generic in ReadOnlySparseVectorStorage

* fix tests
2026-05-09 14:46:21 +02:00
Luis Cossío
6599f545da [UIO] Additional UniversalRead/Write usage simplifications (#8961)
* bytemuck::Pod already implies 'static

* no supertraits

* regions gaps with TypedStorage

* StoredBitSlice with TypedStorage

* bytemuck for PostingsHeader

* bytemuck for TrackerHeader

* bytemuck for MmapRange

* bytemuck for stored_point_to_values::Header
2026-05-08 20:07:03 +02:00
Andrey Vasnetsov
64244330b1 Move element type from UniversalRead trait to method generics (#8955)
* Move element type from UniversalRead trait to method generics

Lifts the `T` parameter off `trait UniversalRead<T>` (and the matching
`UniversalWrite<T>`) and onto the read/write methods themselves. The
`ReadPipeline` associated type becomes a GAT over `T`. With per-method
generics, callers that need to read several element types from one storage
just write `S: UniversalRead` instead of stacking
`UniversalRead<u8> + UniversalRead<Counts> + ...`.

Removes the workarounds the old shape required:
- `TypedStorage<S, T>` newtype (sole purpose was disambiguating multi-bounds)
- `UniversalReadFamily` HKT shim
- `StoredGeoMapIndexStorage` four-bound trait alias
- `CachedSlice<T>` is now non-generic; `T` moves to `get_range`/`len`

No runtime behavior change: alignment in `IoUringRuntime` and `CachedSlice`
is preserved because `T` is still known at each call site.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Restore TypedStorage as a typed-access fail-safe

Reintroduces `TypedStorage<S, T>` as a transparent wrapper around
`UniversalRead`/`UniversalWrite` storage that fixes the element type to
`T`. With per-method generics on the underlying traits, callers can
otherwise read or write any `T` from the same handle; this wrapper
binds it at the type level so accidental cross-type access fails to
compile.

The wrapper exposes inherent typed methods (`read::<P>`, `read_iter`,
`write`, `len`, …) that delegate to the inner storage with `T` fixed.
It does not implement `UniversalRead`/`UniversalWrite` itself — those
are intentionally avoided to prevent the typed binding from being
bypassed via the generic trait methods.

Restores the wrapping at the previous call sites: `StoredStruct`'s
inner storage, `ImmutableIdTracker`'s version mmap, the geo and
numeric index storages, the chunked-vectors chunks, and the
immutable dense vector storage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Silence clippy::len_without_is_empty on TypedStorage

`TypedStorage::len` returns `Result<u64>` (a fallible byte length from
the underlying storage), so an `is_empty` companion would also be
fallible and offer nothing over `len()? == 0`. Suppress the lint at
the impl block, matching how the underlying `UniversalRead::len` is
already exempted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fmt

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 17:37:34 +02:00
qdrant-cloud-bot
0ea777a203 Fix phrase matching crossing string-array element boundaries (#8938)
* Fix phrase matching crossing string-array element boundaries

When indexing a string array like ["quick", "brown"], the index
concatenated tokens from all elements into a single flat document.
A phrase query for "quick brown" found the tokens consecutively,
producing false matches.

Insert a sentinel token ("\x00") between array elements during
indexing. It is registered as a normal vocab token that occupies a
position in the document, so phrase windows cannot span it. No
tokenizer ever produces this string, so it never appears in a query.

Closes #8937

Co-authored-by: Cursor <cursoragent@cursor.com>

* Strip null bytes from tokenizer output

Ensure no tokenizer can produce tokens containing '\0', which is
reserved as the array-boundary sentinel for phrase matching.

Covers all tokenizer paths:
- process_token_cow (used by Word, Whitespace, Prefix, Multilingual)
- Japanese tokenizer (has its own inline processing)

Co-authored-by: Cursor <cursoragent@cursor.com>

* Revert "Strip null bytes from tokenizer output"

This reverts commit fe6ea7a955.

* filter sentinel token from query

* Preallocate vector

Co-authored-by: Tim Visée <tim+github@visee.me>

---------

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: generall <andrey@vasnetsov.com>
Co-authored-by: Tim Visée <tim+github@visee.me>
2026-05-08 16:13:33 +02:00
Luis Cossío
951fbabf9d use UniversalReadFileOps::exists for status file (#8929) 2026-05-08 13:54:20 +02:00
Luis Cossío
dcbbdeba8e [UIO] Genericize ChunkedVectors.status (#8928)
* genericize ChunkedVectors.status, remove `Sized` bound

* check exists with `UniversalReadFileOps`

* Inline UioChunkedVectors bound, drop the alias (#8952)

The empty trait + blanket impl was a stable-Rust trait-alias workaround
that hid a fairly short bound (UniversalWrite<T> + UniversalWrite<Status>
+ Send + 'static) at the cost of an indirection readers had to mentally
unwind. Spelling it out at the three sites that need it is shorter overall
and immediately tells the reader what is required.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Andrey Vasnetsov <andrey@vasnetsov.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 13:51:48 +02:00
Roman Titov
2a47997654 Merge pull request #8791
* Add `EncodedStorage::for_each_in_batch` method

* Implement `for_each_in_batch` method for `QuantizedChunkedMmapStorage`

* Add `EncodedVectors::for_each_in_batch` method

* Add `EncodedVectors::score` method

* Implement `score_stored_batch` for `QuantizedQueryScorer` and `Quanti…

* Remove `TElement` and `TMetric` type parameters from `QuantizedMultiQ…

* Use `QuantizedMultiQueryScorer` when building `raw_internal_scorer`...
2026-05-07 20:20:54 +02:00
Luis Cossío
0957f61072 match exhaustively on match variants (#8947) 2026-05-07 19:46:38 +02:00
xzfc
d8e49327f8 Split mmap_hashmap into multiple files (#8933)
Co-authored-by: generall <andrey@vasnetsov.com>
2026-05-07 14:35:15 +00:00
Ivan Pleshkov
491712424d tq remove data fit option (#8943)
* tq disable data fit option

* remove any mention in grpc
2026-05-07 15:17:31 +02:00
Ivan Pleshkov
d8218ed368 1-2 bits rescoring (#8941) 2026-05-07 14:46:09 +02:00
qdrant-cloud-bot
94db47bb5b Fix nested MatchTextAny falling back to substring matching (#8936) (#8939)
`special_check_condition` in `FieldIndex` did not handle
`Match::TextAny`, so nested-condition evaluation fell through to the
`ValueChecker` fallback which uses naive `String::contains` instead of
proper full-text tokenization. This caused "good" to match "goodness"
inside nested filters.

Add `check_payload_match_any` to `FullTextIndex` and wire it into
`special_check_condition`. Also replace the catch-all `_ => None` with
explicit match arms for all `Match` variants.

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-07 12:42:19 +02:00
Arnaud Gourlay
74bbee0b5f Mmap payload index hardening (#8921) 2026-05-06 13:50:43 +02:00
Daniel Boros
dddc6e9b6f feat/vector-storage-read-only (#8889)
* feat: add vector storage read enum

* fix: linter

* chore: remove open functions

* fix: compiler error

* review fix 1: remove constructiors (for now), use UniversalReadFamily, implement 1st part of VectorStorageReadEnum

* fmt

* read only chucked_vector_storage

* fnt

* feat: add dense chunked variants to vector storage read enum (#8916)

* feat: add multi dense chunked (#8918)

---------

Co-authored-by: generall <andrey@vasnetsov.com>
2026-05-06 13:31:23 +02:00
Andrey Vasnetsov
a10698ce94 Read-only ID tracker (#8887)
* wip: read-only id tracker for mutable data

* read-only id tracker
2026-05-06 12:18:22 +02:00
Arnaud Gourlay
8c354acc70 Text index hardening (#8920) 2026-05-06 11:25:20 +02:00