Commit Graph

3599 Commits

Author SHA1 Message Date
Luca Cominardi 2554072e8f fix: address manual mapping review comments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 14:50:29 +02:00
Luca Cominardi fe1bdf4a54 fix: make fmt 2026-07-22 11:48:17 +02:00
Luca Cominardi 2c967e5d20 refactor: share doc id mapping construction
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 11:46:39 +02:00
Luca Cominardi 2350c8e645 Update src/indexer/segment_writer.rs
Co-authored-by: PSeitz <PSeitz@users.noreply.github.com>
2026-07-22 10:22:51 +02:00
Luca Cominardi 0a071c55f7 fix: clarify manual doc mapping serialization test
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-21 12:41:32 +02:00
Luca Cominardi 56bd23bafe fix: move doc mapping tests to doc mapping module
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-21 12:40:08 +02:00
Luca Cominardi 1c5af9489a fix: cargo fmt 2026-07-07 15:25:35 +02:00
Luca Cominardi 6f7cc10a9b fix: delete remap temp store after single segment finalize
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 15:16:24 +02:00
Luca Cominardi b7234c153e fix: clean up manual mapping finalization invariants
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 13:12:23 +02:00
Luca Cominardi e3be28814e fix: reject default finalize with manual doc id mapping
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 10:56:46 +02:00
Luca Cominardi 3c6eb92c7e fix: restore clone bound on doc id iterator
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 11:31:38 +02:00
Luca Cominardi 9f73d3fe54 docs: clarify manual doc id mapping serialization
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 11:15:53 +02:00
Luca Cominardi 50272240c6 test: move doc id mapping validation coverage
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 11:13:35 +02:00
Luca Cominardi a6fd070e3d fix: validate manual doc id mappings at construction
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 10:52:41 +02:00
Luca Cominardi 06c046bdc9 fix: enable manual doc id mapping in single segment test
The regression test calls finalize_with_doc_id_mapping, so the index must opt into the temporary docstore path before constructing the segment writer.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 17:56:54 +02:00
Luca Cominardi 63f65dcd71 fix: add missing manual_doc_id_mapping field in zstd test IndexSettings initializer
Struct literal was missing the new field introduced in the manual doc id
mapping refactor, causing a compile error under --all-features.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 17:47:33 +02:00
Luca Cominardi 8a4c5b9013 refactor: gate manual doc id mapping via settings
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 17:37:59 +02:00
Luca Cominardi f7355e60cd feat: add single segment doc id mapping finalization
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 16:47:14 +02:00
Luca Cominardi 90603d2396 refactor custom doc id mapping finalization
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 16:24:19 +02:00
Luca Cominardi 910861a3e9 feat: add custom doc id mapping finalization
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 14:45:57 +02:00
trinity-1686a 02e34508e2 Merge pull request #2971 from quickwit-oss/trinity.pointard/fix-slop-overflow
fix overflow on large jumps in linear sequence
2026-06-23 10:18:29 +02:00
trinity-1686a 4031d97bac fix overflow on large jumps in linear sequence
new limit prevent an overflow in eval which caused the residual to be 64b when a slop of zero would give a smaller one
2026-06-23 00:13:27 +02:00
Ming 384f31d350 feat: Restore index sorting (#2959)
We ([ParadeDB](https://github.com/paradedb/paradedb)) have restored and been using the removed [index sorting](https://github.com/quickwit-oss/tantivy/issues/2352) feature in our Tantivy fork.

Our use case is sorting the index by Postgres' internal `ctid` identifier. Results returned from Tantivy must be checked against Postgres' visibility map, and checking them in ctid order is much more cache friendly, resulting in up to 80% speedups for certain queries.

This PR is split into 5 commits, corresponding to the index sorting reversal plus bug fixes we uncovered during our usage of index sorting.

| Commit | Maps to | What it does |
|---|---|---|
| `2aea0ad9f` | foundation ([#104](https://github.com/paradedb/tantivy/pull/104)) | Restore `SegmentComponent::TempStore` (revert of upstream #2815). Subsumes fork PR [#104](https://github.com/paradedb/tantivy/pull/104)'s CI fix. |
| `9205bcb0c` | [#92](https://github.com/paradedb/tantivy/pull/92) | Restore sort-by-field (single-segment + merge paths). |
| `39c790f0f` | [#101](https://github.com/paradedb/tantivy/pull/101) | Enable `sort_by` for `Str`/`Bytes` fast fields. |
| `9c4341a87` | [#105](https://github.com/paradedb/tantivy/pull/105) | Native typed numeric sort-key comparison (precision/NULL fix). |
| `2d9ba2418` | [#106](https://github.com/paradedb/tantivy/pull/106) | Preserve NULL ordering in numeric segment merges. |

We have discussed with the Tantivy maintainers and they indicated they would be open to this PR. Another motivation for landing this PR is we are planning on contributing a significant refactor that makes Tantivy's segment components extensible, and landing that without index sorting leads to too many conflicts.
2026-06-22 11:22:25 -07:00
Pascal Seitz 1e859fd78d fix term aggregation u32::MAX overflow issue 2026-06-18 17:07:43 +08:00
Pascal Seitz f451fa938f explain why naive scorer must accumulate scores in WAND order 2026-06-17 18:58:58 +08:00
Pascal Seitz 2a82dd6f64 fix flaky test 2026-06-17 18:58:58 +08:00
Pascal Seitz c096b2ad89 aggregation/terms: charge fused term_counts to the memory limit
term_counts (one u32/term) was allocated but not charged to
AggregationLimitsGuard, so a memory limit could be exceeded silently.
Charge it, skip allocating it when unbounded, and add a regression test.
2026-06-16 21:23:23 +08:00
Pascal Seitz ac7a3d347c add comment, hoist variables 2026-06-16 21:23:23 +08:00
Pascal Seitz 03520a0719 add top level comment 2026-06-16 21:23:23 +08:00
Pascal Seitz 86a4c47bed merge loops, histo with bounds may benefit from single vec opt 2026-06-16 21:23:23 +08:00
Pascal Seitz fb23e8908f add histogram with bounds 2026-06-16 21:23:23 +08:00
Pascal Seitz 3ca510dff0 aggregation/terms: tidy fused term×histogram grid construction
Rename the value threaded through build_segment_term_collector and
maybe_build_collector from max_term_id to col_max_val/max_column_val — it
is the column's max value, only later reused as the max term id. Make the
grid-size arithmetic overflow-/zero-safe (saturating_add, checked_div).
2026-06-16 21:23:23 +08:00
Pascal Seitz 3cb400c300 clarify counts/term_counts field docs
Spell out that `counts` is the flattened per-term × time-bucket grid (each
term's own contiguous slice) and that `term_counts` is only needed when the
per-term total can't be derived from that grid (i.e. with hard bounds).
2026-06-16 21:23:23 +08:00
Pascal Seitz ef13489d63 skip hard_bounds that can't exclude any value
When a histogram's hard_bounds are wider than the column's value range, the
per-doc `bounds.contains` check can never fail. Collapse such bounds to the
unbounded sentinel in `normalize_histogram_req`, so both the general histogram
hot loop and the fused term×histogram path skip the check — the latter then
derives per-term counts from the grid (the ~17% win) instead of falling back to
per-doc counting just because `bounds != [MIN, MAX]`.

Only the collect-time filter is affected: empty-bucket emission reads
`req.hard_bounds` directly, and hard_bounds only ever clips that range, so a
wider-than-data bound leaves results unchanged. Covered by new tests on the
general and fused paths, including mid-interval (bucket-splitting) bounds.

Also tighten the fused-path u32-overflow guard to bound on `num_vals()` (the
per-value increment count) rather than `num_docs()`, and document why the fused
collector's hot-loop fields are hoisted into locals (re-reading them from memory
each iteration measured ~15% slower).
2026-06-16 21:23:23 +08:00
Pascal Seitz 9f7aea4765 derive term counts 2026-06-16 21:23:23 +08:00
Pascal Seitz 2c8536ab11 add specialized TermHistogram 2026-06-16 21:23:23 +08:00
Pascal Seitz 05f4c02ac5 add dense histogram, optional sub-buckets 2026-06-16 21:23:23 +08:00
Pascal Seitz d137779219 add no sub-gg fastpath 2026-06-16 21:23:23 +08:00
Pascal Seitz 8f9846ac80 use get_range when possible 2026-06-16 21:23:23 +08:00
Pascal Seitz 52e24a9757 add status -> date histogram bench 2026-06-16 21:23:23 +08:00
trinity-1686a 00714326af Merge pull request #2960 from Darkheir/fix/query_grammar_boost_and_escape
fix(query-grammar): Fix issues on boosted and regex queries
2026-06-16 12:03:23 +02:00
Mohammad Dashti 799f7b4646 Built SUM final result in each branch directly.
Keeps the empty-bucket coercion visible at the boundary instead of a
shared binding, following the reviewer's suggested shape.
2026-06-16 03:10:30 +08:00
Mohammad Dashti fc88d80726 docs: drop downstream-specific name from none_if_no_match doc
The flag's purpose is described well enough by "SQL-style consumers";
no need to call out a specific downstream.
2026-06-16 03:10:30 +08:00
Mohammad Dashti 6a684e7c38 feat: opt-in none_if_no_match flag on SumAggregation for SQL-style null
Switch the default serialized output of `sum` on empty / all-missing
buckets back to `"value": 0` to match Elasticsearch, and gate the
SQL-style `"value": null` behavior behind a new
`none_if_no_match: Option<bool>` flag on `SumAggregation`.

`IntermediateSum::finalize` still returns `Option<f64>` internally so
the Rust API stays parallel to min/max/avg, but the ES-vs-SQL choice is
made at the boundary in `IntermediateMetricResult::into_final_metric_result`:
`None` is coerced to `Some(0.0)` unless `none_if_no_match` is set on the
aggregation request.

Adds `AggregationVariants::as_sum()` accessor for that boundary check
and two end-to-end tests covering both the default ES behavior and the
opt-in null behavior on an empty index.
2026-06-16 03:10:30 +08:00
Mohammad Dashti 94fe52cc67 docs: clarify SUM finalize returning None diverges from Elasticsearch
Surface the trade-off in the doc comment so future reviewers see why
this differs from ES (which returns "value": 0 for sum over
empty/all-missing buckets) and what consumers (ParadeDB SQL NULL) the
None variant is meant to serve.
2026-06-16 03:10:30 +08:00
Mohammad Dashti 2ff39f6f7f fix: return None from SUM when no values were collected
IntermediateSum::finalize() returned Some(0.0) even when count==0
(all documents had missing/NULL values). This differs from MIN, MAX,
and AVG which all return None for count==0.

The 0.0 came from IntermediateStats' default sum initialization.
Consumers (like ParadeDB) that map None to SQL NULL were incorrectly
getting 0 for SUM on all-NULL groups.

Fixes paradedb/paradedb#4621
2026-06-16 03:10:30 +08:00
Windforce17 1d06328cb3 Add BlockSegmentPostings::rank() for skip-list-based positional counting
Add a public rank(target) method on BlockSegmentPostings that returns the
number of docs with a doc id strictly smaller than target. It jumps to the
candidate block through the skip list and decodes a single block, so the cost
is O(skip-list entries) + one block decode rather than O(doc_freq).

This is a useful primitive for range counting over a posting list (e.g. number
of matches in a [lo, hi) doc-id window) without iterating every matched doc.

To support it, expose SkipReader::remaining_docs() (pub(crate)). Like seek(),
rank() advances the cursor forward only and must be called with non-decreasing,
valid (<= TERMINATED) targets. Adds a unit test covering multi-block lists and
the below-first / above-last / empty edge cases.
2026-06-15 18:56:49 +08:00
Darkheir 7fd1dbe9f5 fix(query-grammar): Fix issues on boosted and regex queries
Signed-off-by: Darkheir <raphael.cohen@sekoia.io>
2026-06-15 10:50:07 +02:00
Pascal Seitz b19f0ddc77 fix clippy 2026-06-09 23:14:12 +08:00
Pascal Seitz b4acfcf881 cleanup AggregationsSegmentCtx
The metric/cardinality/histogram _mut getters had no callers needing
mutation; their two uses already pass the resulting reference as &T.

simplify req_data ownership: clone into collectors, Rc only for filter BitSet

Replace Vec<Option<Box<T>>> + take/put-back round-trip with Vec<T> +
direct clone into collector. Collectors now own their per-segment
request data outright, removing the borrow-checker dance that the
take/put-back pattern existed to satisfy.

The structural clones are cheap (Column<u64> is Arc-internal) except
for the filter aggregation, whose DocumentQueryEvaluator carries a
precomputed per-segment BitSet sized by max_doc. Wrap that in
Rc<DocumentQueryEvaluator> so FilterAggReqData::clone() bumps a
refcount instead of duplicating the BitSet. Move SegmentFilterCollector's
matching_docs_buffer out of FilterAggReqData so its pre-allocated
capacity is preserved per collector instead of being lost on every clone.
2026-06-09 23:14:12 +08:00