WIP: Support constaints on distance column in KNN queries, for pagination and range queries - #166
Merged
Merged
Conversation
Closed
vlasky added a commit
to vlasky/sqlite-vec
that referenced
this pull request
Nov 28, 2025
Implements WHERE constraints on the distance column in KNN queries, enabling cursor-based pagination and range queries. Based on upstream PR asg017#166 by Alex Garcia with completion and enhancements. Features: - Supports GT, GE, LT, LE operators on distance column - Works with all vector types (float32, int8, bit) - Compatible with partition keys, metadata, and auxiliary columns - Multiple constraints can be combined (e.g., distance >= 3.0 AND distance <= 6.0) Implementation: - Added VEC0_IDXSTR_KIND_KNN_DISTANCE_CONSTRAINT to idxStr encoding - Distance filtering applied during KNN search before top-k selection - Cast f64 to f32 for comparison to match internal precision Enhancements over original PR: - Fixed variable shadowing in inner loops (i -> j) - Added comprehensive test coverage (15 tests) - Fixed bit/int8 vector type handling in tests - Documented precision handling and pagination caveats 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Alex Garcia <alex@alex.garcia> Co-Authored-By: Claude <noreply@anthropic.com>
This was referenced Feb 27, 2026
realgetOff pushed a commit
to realgetOff/sqlite-vec
that referenced
this pull request
Aug 4, 2026
vec0BestIndex() sets aConstraintUsage[].omit = 1 on constraints against the `distance` column, which tells SQLite the vtab will enforce them itself and lets SQLite drop the term from the query plan. Only the FLAT chunk scan ever read those constraints back out of idxStr, so on rescore, DiskANN and IVF columns the predicate was silently discarded and rows violating it were returned with no error. This was a regression: asg017#166 added the distance constraints, and the ANN backends merged six weeks later (asg017#276, asg017#277, asg017#278) never wired them up. Introduce vec0_distance_constraints_satisfied() as the single point of truth for the predicate and route every backend through it: - FLAT keeps its pre-filter semantics; its four nested switch cases collapse into one loop over the chunk bitmap (net -44 lines, snapshots unchanged). - rescore filters the rescored float distances before the top-k truncation, so a lower-bound constraint still yields k rows. Coarse quantized distances from phase 1 are not comparable to a user-supplied threshold, so the filter cannot be pushed down into the quantized scan. - DiskANN and IVF compact their final result sets. Also fixes a latent issue on the rescore path: when the constraint filters out every candidate, result_k becomes 0 and sqlite3_malloc(0) returns NULL, which the existing check would have misreported as SQLITE_NOMEM. Adds parametrized coverage over flat / rescore-bit / rescore-int8 / diskann. 17 of the new assertions fail without this change and pass with it. Fixesasg017#308
realgetOff pushed a commit
to realgetOff/sqlite-vec
that referenced
this pull request
Aug 4, 2026
vec0BestIndex() sets aConstraintUsage[].omit = 1 on constraints against the `distance` column, which tells SQLite the vtab will enforce them itself and lets SQLite drop the term from the query plan. Only the FLAT chunk scan ever read those constraints back out of idxStr, so on rescore, DiskANN and IVF columns the predicate was silently discarded and rows violating it were returned with no error. This was a regression: asg017#166 added the distance constraints, and the ANN backends merged six weeks later (asg017#276, asg017#277, asg017#278) never wired them up. Introduce vec0_distance_constraints_satisfied() as the single point of truth for the predicate and route every backend through it: - FLAT keeps its pre-filter semantics; its four nested switch cases collapse into one loop over the chunk bitmap (net -44 lines, snapshots unchanged). - rescore filters the rescored float distances before the top-k truncation, so a lower-bound constraint still yields k rows. Coarse quantized distances from phase 1 are not comparable to a user-supplied threshold, so the filter cannot be pushed down into the quantized scan. - DiskANN and IVF compact their final result sets. Also fixes a latent issue on the rescore path: when the constraint filters out every candidate, result_k becomes 0 and sqlite3_malloc(0) returns NULL, which the existing check would have misreported as SQLITE_NOMEM. Adds parametrized coverage over flat / rescore-bit / rescore-int8 / diskann, plus ivf behind a build-flag skipif mirroring tests/conftest.py. 17 of the new assertions fail without this change on a default build (22 with IVF enabled) and all pass with it. Fixesasg017#308
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
refs #165
TODO