@@ -792,7 +792,6 @@ def test_sparse_search_forwards_search_params(self, executor, mock_client, mocke
792792 search_params = mock_client .query_points .call_args .kwargs ["search_params" ]
793793 assert search_params .exact is True
794794 assert search_params .indexed_only is True
795-
796795 def test_dense_search_against_hybrid_collection_uses_dense_vector_name (
797796 self , executor , mock_client , mocker
798797 ):
@@ -811,6 +810,55 @@ def test_dense_search_against_hybrid_collection_uses_dense_vector_name(
811810
812811 assert mock_client .query_points .call_args .kwargs ["using" ] == "dense"
813812
813+ def test_dense_search_with_mmr_uses_nearest_query (self , executor , mock_client , mocker ):
814+ from qdrant_client .models import NearestQuery
815+
816+ mock_client .collection_exists .return_value = True
817+ mock_response = mocker .MagicMock ()
818+ mock_response .points = []
819+ mock_client .query_points .return_value = mock_response
820+
821+ node = SearchStmt (
822+ collection = "notes" ,
823+ query_text = "hello" ,
824+ limit = 5 ,
825+ model = None ,
826+ with_clause = SearchWith (mmr_diversity = 0.4 , mmr_candidates = 25 ),
827+ )
828+ executor .execute (node )
829+
830+ query = mock_client .query_points .call_args .kwargs ["query" ]
831+ assert isinstance (query , NearestQuery )
832+ assert query .mmr is not None
833+ assert query .mmr .diversity == pytest .approx (0.4 )
834+ assert query .mmr .candidates_limit == 25
835+
836+ def test_hybrid_search_with_mmr_raises (self , executor , mock_client ):
837+ mock_client .collection_exists .return_value = True
838+ node = SearchStmt (
839+ collection = "notes" ,
840+ query_text = "hello" ,
841+ limit = 5 ,
842+ model = None ,
843+ hybrid = True ,
844+ with_clause = SearchWith (mmr_diversity = 0.5 ),
845+ )
846+ with pytest .raises (QQLRuntimeError , match = "MMR is not supported with USING HYBRID yet" ):
847+ executor .execute (node )
848+
849+ def test_sparse_search_with_mmr_raises (self , executor , mock_client ):
850+ mock_client .collection_exists .return_value = True
851+ node = SearchStmt (
852+ collection = "notes" ,
853+ query_text = "hello" ,
854+ limit = 5 ,
855+ model = None ,
856+ sparse_only = True ,
857+ with_clause = SearchWith (mmr_diversity = 0.5 ),
858+ )
859+ with pytest .raises (QQLRuntimeError , match = "MMR is not supported with USING SPARSE yet" ):
860+ executor .execute (node )
861+
814862
815863class TestRecommend :
816864 def test_recommend_calls_qdrant_query_points (self , executor , mock_client , mocker ):
@@ -1026,6 +1074,17 @@ def test_recommend_forwards_indexed_only_and_quantization(self, executor, mock_c
10261074 assert search_params .quantization is not None
10271075 assert search_params .quantization .rescore is True
10281076
1077+ def test_recommend_with_mmr_raises (self , executor , mock_client ):
1078+ mock_client .collection_exists .return_value = True
1079+ node = RecommendStmt (
1080+ collection = "notes" ,
1081+ positive_ids = ("a" ,),
1082+ limit = 5 ,
1083+ with_clause = SearchWith (mmr_diversity = 0.5 ),
1084+ )
1085+ with pytest .raises (QQLRuntimeError , match = "MMR is supported only for SEARCH statements" ):
1086+ executor .execute (node )
1087+
10291088 def test_recommend_offset_zero_passes_none (self , executor , mock_client , mocker ):
10301089 mock_client .collection_exists .return_value = True
10311090 mock_response = mocker .MagicMock ()
@@ -2268,12 +2327,35 @@ def test_group_by_hybrid_uses_query_points_groups(self, executor, mock_client, m
22682327 collection = "articles" , query_text = "q" , limit = 3 , model = None ,
22692328 hybrid = True , group_by = "category" , group_size = 2 ,
22702329 )
2271- result = executor .execute (node )
2330+ executor .execute (node )
22722331 mock_client .query_points_groups .assert_called_once ()
22732332 kwargs = mock_client .query_points_groups .call_args .kwargs
22742333 assert kwargs ["group_by" ] == "category"
22752334 assert "prefetch" in kwargs
22762335
2336+ def test_group_by_dense_with_mmr_uses_nearest_query (self , executor , mock_client , mocker ):
2337+ from qdrant_client .models import NearestQuery
2338+
2339+ mock_client .collection_exists .return_value = True
2340+ mock_response = mocker .MagicMock ()
2341+ mock_response .groups = []
2342+ mock_client .query_points_groups .return_value = mock_response
2343+
2344+ node = SearchStmt (
2345+ collection = "articles" ,
2346+ query_text = "ai" ,
2347+ limit = 5 ,
2348+ model = None ,
2349+ group_by = "category" ,
2350+ with_clause = SearchWith (mmr_diversity = 0.35 , mmr_candidates = 40 ),
2351+ )
2352+ executor .execute (node )
2353+ query = mock_client .query_points_groups .call_args .kwargs ["query" ]
2354+ assert isinstance (query , NearestQuery )
2355+ assert query .mmr is not None
2356+ assert query .mmr .diversity == pytest .approx (0.35 )
2357+ assert query .mmr .candidates_limit == 40
2358+
22772359
22782360class TestUpdateVector :
22792361 def test_update_vector_calls_update_vectors (self , executor , mock_client ):
@@ -2288,7 +2370,6 @@ def test_update_vector_calls_update_vectors(self, executor, mock_client):
22882370
22892371 def test_update_vector_passes_correct_point_id (self , executor , mock_client ):
22902372 from qql .ast_nodes import UpdateVectorStmt
2291- from qdrant_client .models import PointVectors
22922373 mock_client .collection_exists .return_value = True
22932374 mock_client .get_collection .return_value .config .params .vectors = {} # non-dict → unnamed
22942375 node = UpdateVectorStmt (
@@ -2480,7 +2561,6 @@ def test_update_vector_unnamed_collection_sends_plain_list(self, executor, mock_
24802561 from qql .ast_nodes import UpdateVectorStmt
24812562 mock_client .collection_exists .return_value = True
24822563 # Unnamed collection: get_collection returns non-dict vectors
2483- mock_vectors = mocker .MagicMock () if False else type ("V" , (), {})()
24842564 info = mock_client .get_collection .return_value
24852565 info .config .params .vectors = [None ] # list → not a dict → unnamed
24862566
0 commit comments