@@ -811,6 +811,55 @@ def test_dense_search_against_hybrid_collection_uses_dense_vector_name(
811811
812812 assert mock_client .query_points .call_args .kwargs ["using" ] == "dense"
813813
814+ def test_dense_search_with_mmr_uses_nearest_query (self , executor , mock_client , mocker ):
815+ from qdrant_client .models import NearestQuery
816+
817+ mock_client .collection_exists .return_value = True
818+ mock_response = mocker .MagicMock ()
819+ mock_response .points = []
820+ mock_client .query_points .return_value = mock_response
821+
822+ node = SearchStmt (
823+ collection = "notes" ,
824+ query_text = "hello" ,
825+ limit = 5 ,
826+ model = None ,
827+ with_clause = SearchWith (mmr_diversity = 0.4 , mmr_candidates = 25 ),
828+ )
829+ executor .execute (node )
830+
831+ query = mock_client .query_points .call_args .kwargs ["query" ]
832+ assert isinstance (query , NearestQuery )
833+ assert query .mmr is not None
834+ assert query .mmr .diversity == pytest .approx (0.4 )
835+ assert query .mmr .candidates_limit == 25
836+
837+ def test_hybrid_search_with_mmr_raises (self , executor , mock_client ):
838+ mock_client .collection_exists .return_value = True
839+ node = SearchStmt (
840+ collection = "notes" ,
841+ query_text = "hello" ,
842+ limit = 5 ,
843+ model = None ,
844+ hybrid = True ,
845+ with_clause = SearchWith (mmr_diversity = 0.5 ),
846+ )
847+ with pytest .raises (QQLRuntimeError , match = "MMR is not supported with USING HYBRID yet" ):
848+ executor .execute (node )
849+
850+ def test_sparse_search_with_mmr_raises (self , executor , mock_client ):
851+ mock_client .collection_exists .return_value = True
852+ node = SearchStmt (
853+ collection = "notes" ,
854+ query_text = "hello" ,
855+ limit = 5 ,
856+ model = None ,
857+ sparse_only = True ,
858+ with_clause = SearchWith (mmr_diversity = 0.5 ),
859+ )
860+ with pytest .raises (QQLRuntimeError , match = "MMR is not supported with USING SPARSE yet" ):
861+ executor .execute (node )
862+
814863
815864class TestRecommend :
816865 def test_recommend_calls_qdrant_query_points (self , executor , mock_client , mocker ):
@@ -1026,6 +1075,17 @@ def test_recommend_forwards_indexed_only_and_quantization(self, executor, mock_c
10261075 assert search_params .quantization is not None
10271076 assert search_params .quantization .rescore is True
10281077
1078+ def test_recommend_with_mmr_raises (self , executor , mock_client ):
1079+ mock_client .collection_exists .return_value = True
1080+ node = RecommendStmt (
1081+ collection = "notes" ,
1082+ positive_ids = ("a" ,),
1083+ limit = 5 ,
1084+ with_clause = SearchWith (mmr_diversity = 0.5 ),
1085+ )
1086+ with pytest .raises (QQLRuntimeError , match = "MMR is supported only for SEARCH statements" ):
1087+ executor .execute (node )
1088+
10291089 def test_recommend_offset_zero_passes_none (self , executor , mock_client , mocker ):
10301090 mock_client .collection_exists .return_value = True
10311091 mock_response = mocker .MagicMock ()
@@ -2268,12 +2328,35 @@ def test_group_by_hybrid_uses_query_points_groups(self, executor, mock_client, m
22682328 collection = "articles" , query_text = "q" , limit = 3 , model = None ,
22692329 hybrid = True , group_by = "category" , group_size = 2 ,
22702330 )
2271- result = executor .execute (node )
2331+ executor .execute (node )
22722332 mock_client .query_points_groups .assert_called_once ()
22732333 kwargs = mock_client .query_points_groups .call_args .kwargs
22742334 assert kwargs ["group_by" ] == "category"
22752335 assert "prefetch" in kwargs
22762336
2337+ def test_group_by_dense_with_mmr_uses_nearest_query (self , executor , mock_client , mocker ):
2338+ from qdrant_client .models import NearestQuery
2339+
2340+ mock_client .collection_exists .return_value = True
2341+ mock_response = mocker .MagicMock ()
2342+ mock_response .groups = []
2343+ mock_client .query_points_groups .return_value = mock_response
2344+
2345+ node = SearchStmt (
2346+ collection = "articles" ,
2347+ query_text = "ai" ,
2348+ limit = 5 ,
2349+ model = None ,
2350+ group_by = "category" ,
2351+ with_clause = SearchWith (mmr_diversity = 0.35 , mmr_candidates = 40 ),
2352+ )
2353+ executor .execute (node )
2354+ query = mock_client .query_points_groups .call_args .kwargs ["query" ]
2355+ assert isinstance (query , NearestQuery )
2356+ assert query .mmr is not None
2357+ assert query .mmr .diversity == pytest .approx (0.35 )
2358+ assert query .mmr .candidates_limit == 40
2359+
22772360
22782361class TestUpdateVector :
22792362 def test_update_vector_calls_update_vectors (self , executor , mock_client ):
@@ -2288,7 +2371,6 @@ def test_update_vector_calls_update_vectors(self, executor, mock_client):
22882371
22892372 def test_update_vector_passes_correct_point_id (self , executor , mock_client ):
22902373 from qql .ast_nodes import UpdateVectorStmt
2291- from qdrant_client .models import PointVectors
22922374 mock_client .collection_exists .return_value = True
22932375 mock_client .get_collection .return_value .config .params .vectors = {} # non-dict → unnamed
22942376 node = UpdateVectorStmt (
@@ -2480,7 +2562,6 @@ def test_update_vector_unnamed_collection_sends_plain_list(self, executor, mock_
24802562 from qql .ast_nodes import UpdateVectorStmt
24812563 mock_client .collection_exists .return_value = True
24822564 # Unnamed collection: get_collection returns non-dict vectors
2483- mock_vectors = mocker .MagicMock () if False else type ("V" , (), {})()
24842565 info = mock_client .get_collection .return_value
24852566 info .config .params .vectors = [None ] # list → not a dict → unnamed
24862567
0 commit comments