fix(qdrant): fix MatchText comment and validate logical operator inputs
Corrects the MatchText comment to accurately describe Qdrant behavior (tokenized match with a full-text index; exact substring without one), and adds ValueError validation when AND/OR/NOT filter values are not lists, replacing unhelpful AttributeError crashes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
a930603c12
commit
72c2a7f322
@@ -187,8 +187,10 @@ class Qdrant(VectorStoreBase):
|
|||||||
elif "nin" in value:
|
elif "nin" in value:
|
||||||
return FieldCondition(key=key, match=MatchExcept(**{"except": value["nin"]}))
|
return FieldCondition(key=key, match=MatchExcept(**{"except": value["nin"]}))
|
||||||
elif "contains" in value or "icontains" in value:
|
elif "contains" in value or "icontains" in value:
|
||||||
# MatchText performs case-insensitive substring search on full-text indexed fields.
|
# MatchText: with a full-text index, tokenized matching (all words must appear).
|
||||||
# Note: icontains behaves identically to contains for Qdrant keyword payloads.
|
# Without a full-text index, exact substring match.
|
||||||
|
# Note: icontains maps to the same MatchText — case sensitivity depends on
|
||||||
|
# the full-text index tokenizer configuration, not on this operator name.
|
||||||
text = value.get("contains") or value.get("icontains")
|
text = value.get("contains") or value.get("icontains")
|
||||||
return FieldCondition(key=key, match=MatchText(text=text))
|
return FieldCondition(key=key, match=MatchText(text=text))
|
||||||
else:
|
else:
|
||||||
@@ -220,6 +222,13 @@ class Qdrant(VectorStoreBase):
|
|||||||
must_not = []
|
must_not = []
|
||||||
|
|
||||||
for key, value in filters.items():
|
for key, value in filters.items():
|
||||||
|
if key in ("AND", "OR", "NOT"):
|
||||||
|
if not isinstance(value, list):
|
||||||
|
raise ValueError(
|
||||||
|
f"{key} filter value must be a list of filter dicts, "
|
||||||
|
f"got {type(value).__name__}"
|
||||||
|
)
|
||||||
|
|
||||||
if key == "AND":
|
if key == "AND":
|
||||||
for sub in value:
|
for sub in value:
|
||||||
built = self._create_filter(sub)
|
built = self._create_filter(sub)
|
||||||
|
|||||||
@@ -568,5 +568,20 @@ class TestQdrantEnhancedFilters(unittest.TestCase):
|
|||||||
result = self.qdrant._create_filter({"category": "*"})
|
result = self.qdrant._create_filter({"category": "*"})
|
||||||
self.assertIsNone(result)
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_and_with_non_list_raises_error(self):
|
||||||
|
"""AND with non-list value should raise ValueError."""
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self.qdrant._create_filter({"AND": "not_a_list"})
|
||||||
|
|
||||||
|
def test_or_with_non_list_raises_error(self):
|
||||||
|
"""OR with non-list value should raise ValueError."""
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self.qdrant._create_filter({"OR": {"category": "work"}})
|
||||||
|
|
||||||
|
def test_not_with_non_list_raises_error(self):
|
||||||
|
"""NOT with non-list value should raise ValueError."""
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self.qdrant._create_filter({"NOT": "invalid"})
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
del self.qdrant
|
del self.qdrant
|
||||||
|
|||||||
Reference in New Issue
Block a user