99from  sqlalchemy  import  text 
1010
1111from  langchain_postgres  import  PGEngine , PGVectorStore 
12+ from  langchain_postgres .v2 .hybrid_search_config  import  HybridSearchConfig 
1213from  langchain_postgres .v2 .indexes  import  (
1314    DistanceStrategy ,
1415    HNSWIndex ,
1718from  tests .utils  import  VECTORSTORE_CONNECTION_STRING  as  CONNECTION_STRING 
1819
1920uuid_str  =  str (uuid .uuid4 ()).replace ("-" , "_" )
20- uuid_str_sync  =  str (uuid .uuid4 ()).replace ("-" , "_" )
21+ uuid_str_async  =  str (uuid .uuid4 ()).replace ("-" , "_" )
2122DEFAULT_TABLE  =  "default"  +  uuid_str 
22- DEFAULT_TABLE_ASYNC  =  "default_sync"  +  uuid_str_sync 
23+ DEFAULT_HYBRID_TABLE  =  "hybrid"  +  uuid_str 
24+ DEFAULT_TABLE_ASYNC  =  "default_async"  +  uuid_str_async 
25+ DEFAULT_HYBRID_TABLE_ASYNC  =  "hybrid_async"  +  uuid_str_async 
2326CUSTOM_TABLE  =  "custom"  +  str (uuid .uuid4 ()).replace ("-" , "_" )
2427DEFAULT_INDEX_NAME  =  "index"  +  uuid_str 
25- DEFAULT_INDEX_NAME_ASYNC  =  "index"  +  uuid_str_sync 
28+ DEFAULT_INDEX_NAME_ASYNC  =  "index"  +  uuid_str_async 
2629VECTOR_SIZE  =  768 
2730
2831embeddings_service  =  DeterministicFakeEmbedding (size = VECTOR_SIZE )
@@ -64,6 +67,7 @@ async def engine(self) -> AsyncIterator[PGEngine]:
6467        engine  =  PGEngine .from_connection_string (url = CONNECTION_STRING )
6568        yield  engine 
6669        await  aexecute (engine , f"DROP TABLE IF EXISTS { DEFAULT_TABLE }  )
70+         await  aexecute (engine , f"DROP TABLE IF EXISTS { DEFAULT_HYBRID_TABLE }  )
6771        await  engine .close ()
6872
6973    @pytest_asyncio .fixture (scope = "class" ) 
@@ -118,6 +122,60 @@ async def test_is_valid_index(self, vs: PGVectorStore) -> None:
118122        is_valid  =  vs .is_valid_index ("invalid_index" )
119123        assert  not  is_valid 
120124
125+     async  def  test_apply_hybrid_search_index_non_hybrid_search_vs (
126+         self , vs : PGVectorStore 
127+     ) ->  None :
128+         with  pytest .raises (ValueError ):
129+             vs .apply_hybrid_search_index ()
130+ 
131+     async  def  test_apply_hybrid_search_index_table_without_tsv_column (
132+         self , engine : PGEngine , vs : PGVectorStore 
133+     ) ->  None :
134+         tsv_index_name  =  "tsv_index_on_table_without_tsv_column_"  +  uuid_str 
135+         vs_hybrid  =  PGVectorStore .create_sync (
136+             engine ,
137+             embedding_service = embeddings_service ,
138+             table_name = DEFAULT_TABLE ,
139+             hybrid_search_config = HybridSearchConfig (index_name = tsv_index_name ),
140+         )
141+         is_valid_index  =  vs_hybrid .is_valid_index (tsv_index_name )
142+         assert  is_valid_index  ==  False 
143+         vs_hybrid .apply_hybrid_search_index ()
144+         assert  vs_hybrid .is_valid_index (tsv_index_name )
145+         vs_hybrid .drop_vector_index (tsv_index_name )
146+         is_valid_index  =  vs_hybrid .is_valid_index (tsv_index_name )
147+         assert  is_valid_index  ==  False 
148+ 
149+     async  def  test_apply_hybrid_search_index_table_with_tsv_column (
150+         self , engine : PGEngine 
151+     ) ->  None :
152+         tsv_index_name  =  "tsv_index_on_table_with_tsv_column_"  +  uuid_str 
153+         config  =  HybridSearchConfig (
154+             tsv_column = "tsv_column" ,
155+             tsv_lang = "pg_catalog.english" ,
156+             index_name = tsv_index_name ,
157+         )
158+         engine .init_vectorstore_table (
159+             DEFAULT_HYBRID_TABLE ,
160+             VECTOR_SIZE ,
161+             hybrid_search_config = config ,
162+         )
163+         vs_hybrid  =  PGVectorStore .create_sync (
164+             engine ,
165+             embedding_service = embeddings_service ,
166+             table_name = DEFAULT_HYBRID_TABLE ,
167+             hybrid_search_config = config ,
168+         )
169+         is_valid_index  =  vs_hybrid .is_valid_index (tsv_index_name )
170+         assert  is_valid_index  ==  False 
171+         vs_hybrid .apply_hybrid_search_index ()
172+         assert  vs_hybrid .is_valid_index (tsv_index_name )
173+         vs_hybrid .reindex (tsv_index_name )
174+         assert  vs_hybrid .is_valid_index (tsv_index_name )
175+         vs_hybrid .drop_vector_index (tsv_index_name )
176+         is_valid_index  =  vs_hybrid .is_valid_index (tsv_index_name )
177+         assert  is_valid_index  ==  False 
178+ 
121179
122180@pytest .mark .enable_socket  
123181@pytest .mark .asyncio (scope = "class" ) 
@@ -127,6 +185,7 @@ async def engine(self) -> AsyncIterator[PGEngine]:
127185        engine  =  PGEngine .from_connection_string (url = CONNECTION_STRING )
128186        yield  engine 
129187        await  aexecute (engine , f"DROP TABLE IF EXISTS { DEFAULT_TABLE_ASYNC }  )
188+         await  aexecute (engine , f"DROP TABLE IF EXISTS { DEFAULT_HYBRID_TABLE_ASYNC }  )
130189        await  engine .close ()
131190
132191    @pytest_asyncio .fixture (scope = "class" ) 
@@ -179,3 +238,57 @@ async def test_aapply_vector_index_ivfflat(self, vs: PGVectorStore) -> None:
179238    async  def  test_is_valid_index (self , vs : PGVectorStore ) ->  None :
180239        is_valid  =  await  vs .ais_valid_index ("invalid_index" )
181240        assert  not  is_valid 
241+ 
242+     async  def  test_aapply_hybrid_search_index_non_hybrid_search_vs (
243+         self , vs : PGVectorStore 
244+     ) ->  None :
245+         with  pytest .raises (ValueError ):
246+             await  vs .aapply_hybrid_search_index ()
247+ 
248+     async  def  test_aapply_hybrid_search_index_table_without_tsv_column (
249+         self , engine : PGEngine , vs : PGVectorStore 
250+     ) ->  None :
251+         tsv_index_name  =  "tsv_index_on_table_without_tsv_column_"  +  uuid_str_async 
252+         vs_hybrid  =  await  PGVectorStore .create (
253+             engine ,
254+             embedding_service = embeddings_service ,
255+             table_name = DEFAULT_TABLE_ASYNC ,
256+             hybrid_search_config = HybridSearchConfig (index_name = tsv_index_name ),
257+         )
258+         is_valid_index  =  await  vs_hybrid .ais_valid_index (tsv_index_name )
259+         assert  is_valid_index  ==  False 
260+         await  vs_hybrid .aapply_hybrid_search_index ()
261+         assert  await  vs_hybrid .ais_valid_index (tsv_index_name )
262+         await  vs_hybrid .adrop_vector_index (tsv_index_name )
263+         is_valid_index  =  await  vs_hybrid .ais_valid_index (tsv_index_name )
264+         assert  is_valid_index  ==  False 
265+ 
266+     async  def  test_aapply_hybrid_search_index_table_with_tsv_column (
267+         self , engine : PGEngine 
268+     ) ->  None :
269+         tsv_index_name  =  "tsv_index_on_table_with_tsv_column_"  +  uuid_str_async 
270+         config  =  HybridSearchConfig (
271+             tsv_column = "tsv_column" ,
272+             tsv_lang = "pg_catalog.english" ,
273+             index_name = tsv_index_name ,
274+         )
275+         await  engine .ainit_vectorstore_table (
276+             DEFAULT_HYBRID_TABLE_ASYNC ,
277+             VECTOR_SIZE ,
278+             hybrid_search_config = config ,
279+         )
280+         vs_hybrid  =  await  PGVectorStore .create (
281+             engine ,
282+             embedding_service = embeddings_service ,
283+             table_name = DEFAULT_HYBRID_TABLE_ASYNC ,
284+             hybrid_search_config = config ,
285+         )
286+         is_valid_index  =  await  vs_hybrid .ais_valid_index (tsv_index_name )
287+         assert  is_valid_index  ==  False 
288+         await  vs_hybrid .aapply_hybrid_search_index ()
289+         assert  await  vs_hybrid .ais_valid_index (tsv_index_name )
290+         await  vs_hybrid .areindex (tsv_index_name )
291+         assert  await  vs_hybrid .ais_valid_index (tsv_index_name )
292+         await  vs_hybrid .adrop_vector_index (tsv_index_name )
293+         is_valid_index  =  await  vs_hybrid .ais_valid_index (tsv_index_name )
294+         assert  is_valid_index  ==  False 
0 commit comments