@@ -1079,4 +1079,365 @@ mod tests {
10791079 ) )
10801080 ) ;
10811081 }
1082+
1083+ #[ cfg( feature = "testing" ) ]
1084+ mod proptests {
1085+ use super :: * ;
1086+ use crate :: hnsw_configuration:: Space ;
1087+ use crate :: proptest_utils:: strategies:: {
1088+ embedding_function_strategy, internal_collection_configuration_strategy,
1089+ internal_hnsw_configuration_strategy, internal_spann_configuration_strategy,
1090+ knn_index_strategy,
1091+ } ;
1092+ use crate :: { HnswConfiguration , MetadataValue , SpannConfiguration } ;
1093+ use proptest:: prelude:: * ;
1094+ use proptest:: test_runner:: TestCaseError ;
1095+
1096+ fn space_to_metadata_str ( space : & Space ) -> & ' static str {
1097+ match space {
1098+ Space :: L2 => "l2" ,
1099+ Space :: Cosine => "cosine" ,
1100+ Space :: Ip => "ip" ,
1101+ }
1102+ }
1103+
1104+ fn metadata_from_hnsw_config ( config : & InternalHnswConfiguration ) -> Metadata {
1105+ let mut metadata = Metadata :: new ( ) ;
1106+ metadata. insert (
1107+ "hnsw:space" . to_string ( ) ,
1108+ MetadataValue :: Str ( space_to_metadata_str ( & config. space ) . to_string ( ) ) ,
1109+ ) ;
1110+ metadata. insert (
1111+ "hnsw:construction_ef" . to_string ( ) ,
1112+ MetadataValue :: Int ( config. ef_construction as i64 ) ,
1113+ ) ;
1114+ metadata. insert (
1115+ "hnsw:search_ef" . to_string ( ) ,
1116+ MetadataValue :: Int ( config. ef_search as i64 ) ,
1117+ ) ;
1118+ metadata. insert (
1119+ "hnsw:M" . to_string ( ) ,
1120+ MetadataValue :: Int ( config. max_neighbors as i64 ) ,
1121+ ) ;
1122+ metadata. insert (
1123+ "hnsw:num_threads" . to_string ( ) ,
1124+ MetadataValue :: Int ( config. num_threads as i64 ) ,
1125+ ) ;
1126+ metadata. insert (
1127+ "hnsw:resize_factor" . to_string ( ) ,
1128+ MetadataValue :: Float ( config. resize_factor ) ,
1129+ ) ;
1130+ metadata. insert (
1131+ "hnsw:sync_threshold" . to_string ( ) ,
1132+ MetadataValue :: Int ( config. sync_threshold as i64 ) ,
1133+ ) ;
1134+ metadata. insert (
1135+ "hnsw:batch_size" . to_string ( ) ,
1136+ MetadataValue :: Int ( config. batch_size as i64 ) ,
1137+ ) ;
1138+ metadata
1139+ }
1140+
1141+ fn metadata_hnsw_strategy ( ) -> impl Strategy < Value = ( InternalHnswConfiguration , Metadata ) >
1142+ {
1143+ internal_hnsw_configuration_strategy ( ) . prop_map ( |config| {
1144+ let metadata = metadata_from_hnsw_config ( & config) ;
1145+ ( config, metadata)
1146+ } )
1147+ }
1148+
1149+ fn assert_spann_is_default_with_space (
1150+ config : & InternalSpannConfiguration ,
1151+ expected_space : Space ,
1152+ ) -> Result < ( ) , TestCaseError > {
1153+ let default_config = InternalSpannConfiguration {
1154+ space : expected_space,
1155+ ..InternalSpannConfiguration :: default ( )
1156+ } ;
1157+ prop_assert_eq ! ( config, & default_config) ;
1158+ Ok ( ( ) )
1159+ }
1160+
1161+ fn assert_hnsw_is_default_with_space (
1162+ config : & InternalHnswConfiguration ,
1163+ expected_space : Space ,
1164+ ) -> Result < ( ) , TestCaseError > {
1165+ let default_config = InternalHnswConfiguration {
1166+ space : expected_space,
1167+ ..InternalHnswConfiguration :: default ( )
1168+ } ;
1169+ prop_assert_eq ! ( config, & default_config) ;
1170+ Ok ( ( ) )
1171+ }
1172+
1173+ proptest ! {
1174+ #[ test]
1175+ fn try_from_config_roundtrip_internal(
1176+ internal_config in internal_collection_configuration_strategy( )
1177+ ) {
1178+ let collection_config: CollectionConfiguration = internal_config. clone( ) . into( ) ;
1179+ let default_knn = match & internal_config. vector_index {
1180+ VectorIndexConfiguration :: Hnsw ( _) => KnnIndex :: Hnsw ,
1181+ VectorIndexConfiguration :: Spann ( _) => KnnIndex :: Spann ,
1182+ } ;
1183+
1184+ let result = InternalCollectionConfiguration :: try_from_config(
1185+ collection_config. clone( ) ,
1186+ default_knn,
1187+ None ,
1188+ )
1189+ . expect( "conversion should succeed" ) ;
1190+
1191+ let embedding_function = internal_config. embedding_function. clone( ) ;
1192+ let expected_vector_index = match & internal_config. vector_index {
1193+ VectorIndexConfiguration :: Hnsw ( original) => {
1194+ let external: HnswConfiguration = original. clone( ) . into( ) ;
1195+ let expected_internal: InternalHnswConfiguration = external. clone( ) . into( ) ;
1196+ match & result. vector_index {
1197+ VectorIndexConfiguration :: Hnsw ( converted) => {
1198+ prop_assert_eq!( converted, & expected_internal) ;
1199+ }
1200+ _ => prop_assert!( false , "expected HNSW configuration" ) ,
1201+ }
1202+ VectorIndexConfiguration :: Hnsw ( expected_internal)
1203+ }
1204+ VectorIndexConfiguration :: Spann ( original) => {
1205+ let external: SpannConfiguration = original. clone( ) . into( ) ;
1206+ let expected_internal: InternalSpannConfiguration = external. clone( ) . into( ) ;
1207+ match & result. vector_index {
1208+ VectorIndexConfiguration :: Spann ( converted) => {
1209+ prop_assert_eq!( converted, & expected_internal) ;
1210+ }
1211+ _ => prop_assert!( false , "expected SPANN configuration" ) ,
1212+ }
1213+ VectorIndexConfiguration :: Spann ( expected_internal)
1214+ }
1215+ } ;
1216+
1217+ prop_assert_eq!(
1218+ result. embedding_function. clone( ) ,
1219+ embedding_function. clone( )
1220+ ) ;
1221+ let expected = InternalCollectionConfiguration {
1222+ vector_index: expected_vector_index,
1223+ embedding_function: embedding_function. clone( ) ,
1224+ } ;
1225+ prop_assert_eq!( result, expected) ;
1226+
1227+ let opposite_knn = match & internal_config. vector_index {
1228+ VectorIndexConfiguration :: Hnsw ( _) => KnnIndex :: Spann ,
1229+ VectorIndexConfiguration :: Spann ( _) => KnnIndex :: Hnsw ,
1230+ } ;
1231+ let opposite_result = InternalCollectionConfiguration :: try_from_config(
1232+ collection_config,
1233+ opposite_knn,
1234+ None ,
1235+ )
1236+ . expect( "conversion for opposite default should succeed" ) ;
1237+
1238+ prop_assert_eq!(
1239+ opposite_result. embedding_function. clone( ) ,
1240+ internal_config. embedding_function. clone( )
1241+ ) ;
1242+
1243+ match ( & internal_config. vector_index, & opposite_result. vector_index) {
1244+ ( VectorIndexConfiguration :: Hnsw ( original) , VectorIndexConfiguration :: Spann ( spann) ) => {
1245+ let expected_space = original. space. clone( ) ;
1246+ assert_spann_is_default_with_space( spann, expected_space) ?;
1247+ }
1248+ ( VectorIndexConfiguration :: Spann ( original) , VectorIndexConfiguration :: Hnsw ( hnsw) ) => {
1249+ let expected_space = original. space. clone( ) ;
1250+ assert_hnsw_is_default_with_space( hnsw, expected_space) ?;
1251+ }
1252+ _ => prop_assert!( false , "unexpected opposite conversion result" ) ,
1253+ }
1254+ }
1255+ }
1256+
1257+ proptest ! {
1258+ #[ test]
1259+ fn try_from_config_uses_metadata_when_configs_absent(
1260+ ( expected_hnsw, metadata) in metadata_hnsw_strategy( ) ,
1261+ embedding in embedding_function_strategy( ) ,
1262+ knn in knn_index_strategy( ) ,
1263+ ) {
1264+ let collection_config = CollectionConfiguration {
1265+ hnsw: None ,
1266+ spann: None ,
1267+ embedding_function: embedding. clone( ) ,
1268+ } ;
1269+
1270+ let result = InternalCollectionConfiguration :: try_from_config(
1271+ collection_config,
1272+ knn,
1273+ Some ( metadata. clone( ) ) ,
1274+ )
1275+ . expect( "conversion should succeed" ) ;
1276+
1277+ match ( knn, & result. vector_index) {
1278+ ( KnnIndex :: Hnsw , VectorIndexConfiguration :: Hnsw ( hnsw) ) => {
1279+ prop_assert_eq!( hnsw, & expected_hnsw) ;
1280+ }
1281+ ( KnnIndex :: Spann , VectorIndexConfiguration :: Spann ( spann) ) => {
1282+ prop_assert_eq!( spann. space. clone( ) , expected_hnsw. space. clone( ) ) ;
1283+ assert_spann_is_default_with_space( spann, expected_hnsw. space. clone( ) ) ?;
1284+ }
1285+ _ => prop_assert!( false , "unexpected vector index variant" ) ,
1286+ }
1287+ prop_assert_eq!( result. embedding_function. clone( ) , embedding) ;
1288+ }
1289+ }
1290+
1291+ proptest ! {
1292+ #[ test]
1293+ fn try_from_config_uses_metadata_when_hnsw_config_is_default_values(
1294+ ( expected_hnsw, metadata) in metadata_hnsw_strategy( ) ,
1295+ embedding in embedding_function_strategy( ) ,
1296+ ) {
1297+ let collection_config = CollectionConfiguration {
1298+ hnsw: Some ( HnswConfiguration :: default ( ) ) ,
1299+ spann: None ,
1300+ embedding_function: embedding. clone( ) ,
1301+ } ;
1302+
1303+ let result = InternalCollectionConfiguration :: try_from_config(
1304+ collection_config,
1305+ KnnIndex :: Hnsw ,
1306+ Some ( metadata. clone( ) ) ,
1307+ )
1308+ . expect( "conversion should succeed" ) ;
1309+
1310+ match & result. vector_index {
1311+ VectorIndexConfiguration :: Hnsw ( hnsw) => {
1312+ prop_assert_eq!( hnsw, & expected_hnsw) ;
1313+ }
1314+ _ => prop_assert!( false , "expected hnsw configuration" ) ,
1315+ }
1316+ prop_assert_eq!( result. embedding_function. clone( ) , embedding) ;
1317+ }
1318+ }
1319+
1320+ proptest ! {
1321+ #[ test]
1322+ fn try_from_config_prefers_spann_when_default_is_spann(
1323+ hnsw_config in internal_hnsw_configuration_strategy( ) ,
1324+ embedding in embedding_function_strategy( ) ,
1325+ ) {
1326+ let embedding_clone = embedding. clone( ) ;
1327+ let collection_config = CollectionConfiguration {
1328+ hnsw: Some ( hnsw_config. clone( ) . into( ) ) ,
1329+ spann: None ,
1330+ embedding_function: embedding,
1331+ } ;
1332+
1333+ let result = InternalCollectionConfiguration :: try_from_config(
1334+ collection_config,
1335+ KnnIndex :: Spann ,
1336+ None ,
1337+ )
1338+ . expect( "conversion should succeed" ) ;
1339+
1340+ let expected_space = hnsw_config. space. clone( ) ;
1341+ match & result. vector_index {
1342+ VectorIndexConfiguration :: Spann ( spann) => {
1343+ prop_assert_eq!( spann. space. clone( ) , expected_space. clone( ) ) ;
1344+ assert_spann_is_default_with_space( spann, expected_space) ?;
1345+ }
1346+ _ => prop_assert!( false , "expected spann configuration" ) ,
1347+ }
1348+ prop_assert_eq!( result. embedding_function. clone( ) , embedding_clone) ;
1349+ }
1350+ }
1351+
1352+ proptest ! {
1353+ #[ test]
1354+ fn try_from_config_prefers_hnsw_when_default_is_hnsw(
1355+ spann_config in internal_spann_configuration_strategy( ) ,
1356+ embedding in embedding_function_strategy( ) ,
1357+ ) {
1358+ let embedding_clone = embedding. clone( ) ;
1359+ let collection_config = CollectionConfiguration {
1360+ hnsw: None ,
1361+ spann: Some ( spann_config. clone( ) . into( ) ) ,
1362+ embedding_function: embedding,
1363+ } ;
1364+
1365+ let result = InternalCollectionConfiguration :: try_from_config(
1366+ collection_config,
1367+ KnnIndex :: Hnsw ,
1368+ None ,
1369+ )
1370+ . expect( "conversion should succeed" ) ;
1371+
1372+ let expected_space = spann_config. space. clone( ) ;
1373+ match & result. vector_index {
1374+ VectorIndexConfiguration :: Hnsw ( hnsw) => {
1375+ prop_assert_eq!( hnsw. space. clone( ) , expected_space. clone( ) ) ;
1376+ assert_hnsw_is_default_with_space( hnsw, expected_space) ?;
1377+ }
1378+ _ => prop_assert!( false , "expected hnsw configuration" ) ,
1379+ }
1380+ prop_assert_eq!( result. embedding_function. clone( ) , embedding_clone) ;
1381+ }
1382+ }
1383+
1384+ proptest ! {
1385+ #[ test]
1386+ fn try_from_config_defaults_when_configs_absent(
1387+ embedding in embedding_function_strategy( ) ,
1388+ knn in knn_index_strategy( ) ,
1389+ ) {
1390+ let collection_config = CollectionConfiguration {
1391+ hnsw: None ,
1392+ spann: None ,
1393+ embedding_function: embedding. clone( ) ,
1394+ } ;
1395+
1396+ let result = InternalCollectionConfiguration :: try_from_config(
1397+ collection_config,
1398+ knn,
1399+ None ,
1400+ )
1401+ . expect( "conversion should succeed" ) ;
1402+
1403+ match ( knn, & result. vector_index) {
1404+ ( KnnIndex :: Hnsw , VectorIndexConfiguration :: Hnsw ( hnsw) ) => {
1405+ prop_assert_eq!( hnsw, & InternalHnswConfiguration :: default ( ) ) ;
1406+ }
1407+ ( KnnIndex :: Spann , VectorIndexConfiguration :: Spann ( spann) ) => {
1408+ prop_assert_eq!( spann, & InternalSpannConfiguration :: default ( ) ) ;
1409+ }
1410+ _ => prop_assert!( false , "unexpected vector index variant" ) ,
1411+ }
1412+ prop_assert_eq!( result. embedding_function. clone( ) , embedding) ;
1413+ }
1414+ }
1415+
1416+ proptest ! {
1417+ #[ test]
1418+ fn try_from_config_errors_on_multiple_configs(
1419+ hnsw_config in internal_hnsw_configuration_strategy( ) ,
1420+ spann_config in internal_spann_configuration_strategy( ) ,
1421+ embedding in embedding_function_strategy( ) ,
1422+ knn in knn_index_strategy( ) ,
1423+ ) {
1424+ let collection_config = CollectionConfiguration {
1425+ hnsw: Some ( hnsw_config. into( ) ) ,
1426+ spann: Some ( spann_config. into( ) ) ,
1427+ embedding_function: embedding,
1428+ } ;
1429+
1430+ let result = InternalCollectionConfiguration :: try_from_config(
1431+ collection_config,
1432+ knn,
1433+ None ,
1434+ ) ;
1435+
1436+ prop_assert!( matches!(
1437+ result,
1438+ Err ( CollectionConfigurationToInternalConfigurationError :: MultipleVectorIndexConfigurations )
1439+ ) ) ;
1440+ }
1441+ }
1442+ }
10821443}
0 commit comments