Fix match: {except: []} returning zero results with payload index#9055
Conversation
Regression test for #9050 An empty `except` list (NOT IN []) should always match all points that have the field, both with and without a payload index. Currently the integer (and keyword) indexed path incorrectly returns zero results because: 1. serde deserializes `except: []` as `AnyVariants::Strings([])` (first variant of the untagged enum) 2. The map index filter_impl returns `iter::empty()` for empty cross-type variant, instead of matching everything The test covers: - except: [] without any index (baseline, currently works) - except: [] with an integer index (currently broken) - except: [] with a keyword index (currently broken) Co-authored-by: Cursor <[email protected]>
Fixes #9050 Root cause: `except: []` deserializes as `AnyVariants::Strings([])` due to the untagged serde enum trying `Strings` first. On an integer index, the filter_impl matched `Except + Strings(empty)` and returned `iter::empty()` (zero results). The same issue existed symmetrically on keyword indexes with `Integers(empty)` and on UUID indexes with `Integers(empty)`. The fix: when the cross-type variant reaches the Except branch (e.g. Strings on an integer index, Integers on a keyword/UUID index), return `None` unconditionally — regardless of whether the set is empty. This delegates to the fallback condition checker, which already handles `except: []` correctly by matching all values. The `estimate_cardinality_impl` functions had the same bug (returning `CardinalityEstimation::exact(0)` for the empty cross-type case) and are fixed the same way. Affected index types: integer, keyword (str), UUID. Bool index is NOT affected — it already returns `None` for all Any/Except conditions. Co-authored-by: Cursor <[email protected]>
1cfc2a5 to
9f7038c
Compare
Extends the regression test for #9050 to comprehensively cover: - Integer, keyword, and UUID field indexes - Empty except list (the original bug) for each index type - Non-empty except list with matching types (normal filtering) - Cross-type except values (e.g. strings on integer index, integers on keyword/UUID index) — type mismatch should exclude nothing → all match - Exclude-all: listing every value should return zero results - All scenarios tested both with and without a payload index Co-authored-by: Cursor <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/openapi/test_match_except_empty.py`:
- Around line 162-166: The test suite currently relies on test_create_indexes()
to create indexes via _create_index(COLLECTION_NAME, ...), which fails under
pytest-xdist because tests with indexes may run in different workers; move the
index creation into a module-scoped fixture (e.g., define a
`@pytest.fixture`(scope="module") create_indexes that calls
_create_index(COLLECTION_NAME, "n", "integer"), _create_index(COLLECTION_NAME,
"tag", "keyword"), and _create_index(COLLECTION_NAME, "uid", "uuid")) and have
the indexed tests (those named like test_match_except_empty_*_with_index) depend
on that fixture, or alternatively perform the same index calls in the module
setup fixture (setup_module) so all workers use the same module-scoped setup
instead of relying on test_create_indexes().
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: c85ba8eb-e0a4-46b8-bfdd-897d70631192
📒 Files selected for processing (1)
tests/openapi/test_match_except_empty.py
| def test_create_indexes(): | ||
| _create_index(COLLECTION_NAME, "n", "integer") | ||
| _create_index(COLLECTION_NAME, "tag", "keyword") | ||
| _create_index(COLLECTION_NAME, "uid", "uuid") | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether pytest order-randomization plugins/config are present,
# which would make current order-coupled tests flaky.
set -euo pipefail
echo "== pytest-related config files =="
fd -i 'pytest\.ini|pyproject\.toml|tox\.ini|setup\.cfg'
echo
echo "== search for random-order plugins/config =="
rg -n -C2 'pytest-randomly|random-order|randomly|--random|addopts' \
pyproject.toml pytest.ini tox.ini setup.cfg requirements*.txt **/conftest.py 2>/dev/null || true
echo
echo "== confirm indexed tests depend on explicit index-creation test =="
rg -n -C2 'def test_create_indexes|def test_match_except_.*with_index|def test_match_except_.*on_.*index' \
tests/openapi/test_match_except_empty.pyRepository: qdrant/qdrant
Length of output: 2906
🏁 Script executed:
cat tests/pyproject.tomlRepository: qdrant/qdrant
Length of output: 1185
🏁 Script executed:
head -70 tests/openapi/test_match_except_empty.pyRepository: qdrant/qdrant
Length of output: 2032
🏁 Script executed:
rg -n '@pytest.mark.xdist_group|xdist_group' tests/openapi/test_match_except_empty.py | head -20Repository: qdrant/qdrant
Length of output: 39
🏁 Script executed:
sed -n '162,210p' tests/openapi/test_match_except_empty.pyRepository: qdrant/qdrant
Length of output: 1856
Move index creation to a fixture to avoid worker isolation issues with pytest-xdist.
Tests test_match_except_empty_*_with_index and other indexed cases at lines 172–305 depend on test_create_indexes() (line 162) running first. With pytest-xdist's --dist=loadgroup strategy configured in tests/pyproject.toml, indexed tests can be distributed to a different worker than test_create_indexes(), causing them to fail silently (each worker runs the module fixture independently, creating an empty collection without the required indexes). Move index creation into a module-scoped fixture that indexed tests depend on, or create indexes directly in the module setup fixture.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/openapi/test_match_except_empty.py` around lines 162 - 166, The test
suite currently relies on test_create_indexes() to create indexes via
_create_index(COLLECTION_NAME, ...), which fails under pytest-xdist because
tests with indexes may run in different workers; move the index creation into a
module-scoped fixture (e.g., define a `@pytest.fixture`(scope="module")
create_indexes that calls _create_index(COLLECTION_NAME, "n", "integer"),
_create_index(COLLECTION_NAME, "tag", "keyword"), and
_create_index(COLLECTION_NAME, "uid", "uuid")) and have the indexed tests (those
named like test_match_except_empty_*_with_index) depend on that fixture, or
alternatively perform the same index calls in the module setup fixture
(setup_module) so all workers use the same module-scoped setup instead of
relying on test_create_indexes().
…9055) * Add integration test for `match: {except: []}` with integer index Regression test for #9050 An empty `except` list (NOT IN []) should always match all points that have the field, both with and without a payload index. Currently the integer (and keyword) indexed path incorrectly returns zero results because: 1. serde deserializes `except: []` as `AnyVariants::Strings([])` (first variant of the untagged enum) 2. The map index filter_impl returns `iter::empty()` for empty cross-type variant, instead of matching everything The test covers: - except: [] without any index (baseline, currently works) - except: [] with an integer index (currently broken) - except: [] with a keyword index (currently broken) Co-authored-by: Cursor <[email protected]> * Fix `match: {except: []}` returning zero results with payload index Fixes #9050 Root cause: `except: []` deserializes as `AnyVariants::Strings([])` due to the untagged serde enum trying `Strings` first. On an integer index, the filter_impl matched `Except + Strings(empty)` and returned `iter::empty()` (zero results). The same issue existed symmetrically on keyword indexes with `Integers(empty)` and on UUID indexes with `Integers(empty)`. The fix: when the cross-type variant reaches the Except branch (e.g. Strings on an integer index, Integers on a keyword/UUID index), return `None` unconditionally — regardless of whether the set is empty. This delegates to the fallback condition checker, which already handles `except: []` correctly by matching all values. The `estimate_cardinality_impl` functions had the same bug (returning `CardinalityEstimation::exact(0)` for the empty cross-type case) and are fixed the same way. Affected index types: integer, keyword (str), UUID. Bool index is NOT affected — it already returns `None` for all Any/Except conditions. Co-authored-by: Cursor <[email protected]> * Expand match-except test to cover all index types and cross-type filters Extends the regression test for #9050 to comprehensively cover: - Integer, keyword, and UUID field indexes - Empty except list (the original bug) for each index type - Non-empty except list with matching types (normal filtering) - Cross-type except values (e.g. strings on integer index, integers on keyword/UUID index) — type mismatch should exclude nothing → all match - Exclude-all: listing every value should return zero results - All scenarios tested both with and without a payload index Co-authored-by: Cursor <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Cursor <[email protected]>
Summary
Fixes #9050
match: {"except": []}(NOT IN empty list) incorrectly returns zero results when a payload index exists on the fieldexcept: []asAnyVariants::Strings([])(first variant of untagged enum); the map indexfilter_implthen returnsiter::empty()for the empty cross-type case instead of delegating to the condition checkerNonefor cross-typeExceptvariants unconditionally (not just when non-empty), so the fallback condition checker handles the query correctlyestimate_cardinality_implwhich had the sameCardinalityEstimation::exact(0)bugTest plan
except: []without index (baseline, was working)except: []with integer index (was broken)except: []with keyword index (was broken)cargo check -p segmentpasses locallyMade with Cursor