chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
# Copyright 2025-present the zvec project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""Tests for FTS (Full-Text Search) query support in the Python SDK."""
|
||||
|
||||
import pickle
|
||||
|
||||
import pytest
|
||||
|
||||
from zvec.model.param.query import Fts, Query
|
||||
|
||||
|
||||
class TestFtsQueryValidation:
|
||||
"""Test FTS parameter validation in Query dataclass."""
|
||||
|
||||
def test_fts_query_string_only(self):
|
||||
"""Query with only query_string in Fts should be valid."""
|
||||
q = Query(
|
||||
field_name="content", fts=Fts(query_string='+hello -world "exact phrase"')
|
||||
)
|
||||
q._validate()
|
||||
assert q.fts.query_string == '+hello -world "exact phrase"'
|
||||
assert q.fts.match_string is None
|
||||
assert q.has_fts() is True
|
||||
|
||||
def test_fts_match_string_only(self):
|
||||
"""Query with only match_string in Fts should be valid."""
|
||||
q = Query(field_name="content", fts=Fts(match_string="machine learning"))
|
||||
q._validate()
|
||||
assert q.fts.match_string == "machine learning"
|
||||
assert q.fts.query_string is None
|
||||
assert q.has_fts() is True
|
||||
|
||||
def test_fts_query_string_and_match_string_mutually_exclusive(self):
|
||||
"""Cannot provide both query_string and match_string in Fts."""
|
||||
q = Query(
|
||||
field_name="content",
|
||||
fts=Fts(query_string="+hello", match_string="hello world"),
|
||||
)
|
||||
with pytest.raises(ValueError, match="mutually exclusive"):
|
||||
q._validate()
|
||||
|
||||
def test_no_fts(self):
|
||||
"""Query without FTS fields should have has_fts() == False."""
|
||||
q = Query(field_name="embedding", vector=[0.1, 0.2, 0.3])
|
||||
assert q.has_fts() is False
|
||||
|
||||
def test_vector_and_fts_mutually_exclusive(self):
|
||||
"""Cannot combine vector search with FTS in a single Query."""
|
||||
q = Query(
|
||||
field_name="embedding",
|
||||
vector=[0.1, 0.2, 0.3],
|
||||
fts=Fts(match_string="deep learning"),
|
||||
)
|
||||
with pytest.raises(ValueError, match="Cannot combine fts with vector search"):
|
||||
q._validate()
|
||||
|
||||
def test_fts_without_vector_or_id(self):
|
||||
"""Query with only FTS (no vector, no id) should be valid."""
|
||||
q = Query(field_name="content", fts=Fts(query_string="hello"))
|
||||
q._validate()
|
||||
assert q.has_vector() is False
|
||||
assert q.has_id() is False
|
||||
assert q.has_fts() is True
|
||||
|
||||
|
||||
class TestFtsQueryBinding:
|
||||
"""Test FTS binding layer (_Fts)."""
|
||||
|
||||
def test_import_fts_query(self):
|
||||
"""_Fts should be importable from _zvec.param."""
|
||||
from zvec._zvec.param import _Fts
|
||||
|
||||
fts = _Fts()
|
||||
assert fts.query_string == ""
|
||||
assert fts.match_string == ""
|
||||
|
||||
def test_fts_query_set_fields(self):
|
||||
"""Setting fields on _Fts should work."""
|
||||
from zvec._zvec.param import _Fts
|
||||
|
||||
fts = _Fts()
|
||||
fts.query_string = "+hello -world"
|
||||
assert fts.query_string == "+hello -world"
|
||||
|
||||
fts2 = _Fts()
|
||||
fts2.match_string = "machine learning"
|
||||
assert fts2.match_string == "machine learning"
|
||||
|
||||
def test_fts_query_pickle(self):
|
||||
"""_Fts should support pickling."""
|
||||
from zvec._zvec.param import _Fts
|
||||
|
||||
fts = _Fts()
|
||||
fts.query_string = "+vector search"
|
||||
fts.match_string = ""
|
||||
|
||||
data = pickle.dumps(fts)
|
||||
restored = pickle.loads(data)
|
||||
assert restored.query_string == "+vector search"
|
||||
assert restored.match_string == ""
|
||||
|
||||
def test_search_query_fts_field(self):
|
||||
"""_SearchQuery should have fts field."""
|
||||
from zvec._zvec.param import _Fts, _SearchQuery
|
||||
|
||||
vq = _SearchQuery()
|
||||
# fts should be None by default (optional)
|
||||
assert vq.fts is None
|
||||
|
||||
# set fts
|
||||
fts = _Fts()
|
||||
fts.query_string = "hello"
|
||||
vq.fts = fts
|
||||
assert vq.fts is not None
|
||||
assert vq.fts.query_string == "hello"
|
||||
|
||||
def test_search_query_pickle_with_fts(self):
|
||||
"""_SearchQuery with fts should survive pickling."""
|
||||
from zvec._zvec.param import _Fts, _SearchQuery
|
||||
|
||||
vq = _SearchQuery()
|
||||
vq.topk = 10
|
||||
vq.field_name = "embedding"
|
||||
fts = _Fts()
|
||||
fts.match_string = "test query"
|
||||
vq.fts = fts
|
||||
|
||||
data = pickle.dumps(vq)
|
||||
restored = pickle.loads(data)
|
||||
assert restored.topk == 10
|
||||
assert restored.field_name == "embedding"
|
||||
assert restored.fts is not None
|
||||
assert restored.fts.match_string == "test query"
|
||||
|
||||
def test_search_query_pickle_without_fts(self):
|
||||
"""_SearchQuery without fts should survive pickling."""
|
||||
from zvec._zvec.param import _SearchQuery
|
||||
|
||||
vq = _SearchQuery()
|
||||
vq.topk = 5
|
||||
vq.field_name = "vec"
|
||||
|
||||
data = pickle.dumps(vq)
|
||||
restored = pickle.loads(data)
|
||||
assert restored.topk == 5
|
||||
assert restored.field_name == "vec"
|
||||
assert restored.fts is None
|
||||
Reference in New Issue
Block a user