18
18
19
19
import jakarta .persistence .EntityManagerFactory ;
20
20
21
+ import org .hibernate .search .backend .elasticsearch .ElasticsearchExtension ;
21
22
import org .hibernate .search .documentation .testsupport .BackendConfigurations ;
22
23
import org .hibernate .search .documentation .testsupport .DocumentationSetupHelper ;
23
24
import org .hibernate .search .engine .search .common .BooleanOperator ;
32
33
import org .hibernate .search .mapper .orm .mapping .HibernateOrmSearchMappingConfigurer ;
33
34
import org .hibernate .search .mapper .orm .scope .SearchScope ;
34
35
import org .hibernate .search .mapper .orm .session .SearchSession ;
36
+ import org .hibernate .search .mapper .pojo .mapping .definition .programmatic .TypeMappingStep ;
35
37
import org .hibernate .search .util .common .data .RangeBoundInclusion ;
38
+ import org .hibernate .search .util .impl .integrationtest .backend .elasticsearch .dialect .ElasticsearchTestDialect ;
36
39
import org .hibernate .search .util .impl .integrationtest .common .extension .BackendConfiguration ;
37
40
38
41
import org .junit .jupiter .api .BeforeEach ;
@@ -54,20 +57,33 @@ class PredicateDslIT {
54
57
55
58
private EntityManagerFactory entityManagerFactory ;
56
59
60
+ private static boolean isVectorSearchSupported () {
61
+ return BackendConfiguration .isLucene ()
62
+ || ElasticsearchTestDialect .isActualVersion (
63
+ es -> !es .isLessThan ( "8.0" ),
64
+ os -> !os .isLessThan ( "2.0" ),
65
+ aoss -> true
66
+ );
67
+ }
68
+
57
69
@ BeforeEach
58
70
void setup () {
59
71
entityManagerFactory = setupHelper .start ().setup ( Book .class , Author .class , EmbeddableGeoPoint .class );
60
72
61
73
DocumentationSetupHelper .SetupContext setupContext = setupHelper .start ();
62
- // NOTE: To keep this documentation example simple there is no testing with Elasticsearch/OpenSearch
63
- // as not all versions have integration implemented e.g. Elasticsearch 7 or OpenSearch 1.3 will throw exceptions
64
- if ( BackendConfiguration . isLucene () ) {
74
+ // NOTE: If backend does not support vector search it will lead to runtime exceptions, so we cannot simply annotate
75
+ // the corresponding properties with @VectorField; instead we add it programmatically when it's possible
76
+ if ( isVectorSearchSupported () ) {
65
77
setupContext .withProperty (
66
78
HibernateOrmMapperSettings .MAPPING_CONFIGURER ,
67
- (HibernateOrmSearchMappingConfigurer ) context -> context .programmaticMapping ()
68
- .type ( Book .class )
69
- .property ( "coverImageEmbeddings" )
70
- .vectorField ( 128 )
79
+ (HibernateOrmSearchMappingConfigurer ) context -> {
80
+ TypeMappingStep book = context .programmaticMapping ()
81
+ .type ( Book .class );
82
+ book .property ( "coverImageEmbeddings" )
83
+ .vectorField ( 128 );
84
+ book .property ( "alternativeCoverImageEmbeddings" )
85
+ .vectorField ( 128 );
86
+ }
71
87
);
72
88
}
73
89
entityManagerFactory = setupContext .setup ( Book .class , Author .class , EmbeddableGeoPoint .class );
@@ -1110,14 +1126,14 @@ void knn() {
1110
1126
// NOTE: To keep this documentation example simple there is no testing with Elasticsearch/OpenSearch
1111
1127
// as not all versions have integration implemented e.g. Elasticsearch 7 or OpenSearch 1.3 will throw exceptions
1112
1128
assumeTrue (
1113
- BackendConfiguration . isLucene (),
1129
+ isVectorSearchSupported (),
1114
1130
"This test only makes sense if the backend supports vectors"
1115
1131
);
1116
1132
withinSearchSession ( searchSession -> {
1117
1133
// tag::knn[]
1118
1134
float [] coverImageEmbeddingsVector = /*...*/
1119
1135
// end::knn[]
1120
- new float [ 128 ] ;
1136
+ floats ( 128 , 1.0f ) ;
1121
1137
// tag::knn[]
1122
1138
List <Book > hits = searchSession .search ( Book .class )
1123
1139
.where ( f -> f .knn ( 5 ).field ( "coverImageEmbeddings" ).matching ( coverImageEmbeddingsVector ) )
@@ -1132,7 +1148,7 @@ void knn() {
1132
1148
// tag::knn-filter[]
1133
1149
float [] coverImageEmbeddingsVector = /*...*/
1134
1150
// end::knn-filter[]
1135
- new float [ 128 ] ;
1151
+ floats ( 128 , 1.0f ) ;
1136
1152
// tag::knn-filter[]
1137
1153
List <Book > hits = searchSession .search ( Book .class )
1138
1154
.where ( f -> f .knn ( 5 ).field ( "coverImageEmbeddings" ).matching ( coverImageEmbeddingsVector )
@@ -1143,6 +1159,74 @@ void knn() {
1143
1159
.extracting ( Book ::getId )
1144
1160
.containsExactlyInAnyOrder ( BOOK1_ID , BOOK2_ID , BOOK3_ID );
1145
1161
} );
1162
+
1163
+ withinSearchSession ( searchSession -> {
1164
+ // tag::knn-should[]
1165
+ float [] coverImageEmbeddingsVector = /*...*/
1166
+ // end::knn-should[]
1167
+ floats ( 128 , 1.0f );
1168
+ // tag::knn-should[]
1169
+ float [] alternativeCoverImageEmbeddingsVector = /*...*/
1170
+ // end::knn-should[]
1171
+ floats ( 128 , 1.0f );
1172
+ // tag::knn-should[]
1173
+ List <Book > hits = searchSession .search ( Book .class )
1174
+ .where ( f -> f .bool ()
1175
+ .should ( f .knn ( 10 ).field ( "coverImageEmbeddings" ).matching ( coverImageEmbeddingsVector ) )
1176
+ .should ( f .knn ( 5 ).field ( "alternativeCoverImageEmbeddings" )
1177
+ .matching ( alternativeCoverImageEmbeddingsVector ) )
1178
+ )
1179
+ .fetchHits ( 20 );
1180
+ // end::knn-should[]
1181
+ assertThat ( hits )
1182
+ .extracting ( Book ::getId )
1183
+ .containsExactlyInAnyOrder ( BOOK1_ID , BOOK2_ID , BOOK3_ID , BOOK4_ID );
1184
+ } );
1185
+
1186
+ if ( !BackendConfiguration .isElasticsearch ()
1187
+ || ElasticsearchTestDialect .isActualVersion (
1188
+ es -> false ,
1189
+ os -> !os .isLessThan ( "2.0" ),
1190
+ aoss -> true
1191
+ ) ) {
1192
+ withinSearchSession ( searchSession -> {
1193
+ // tag::knn-and-match[]
1194
+ float [] coverImageEmbeddingsVector = /*...*/
1195
+ // end::knn-and-match[]
1196
+ floats ( 128 , 1.0f );
1197
+ // tag::knn-and-match[]
1198
+ List <Book > hits = searchSession .search ( Book .class )
1199
+ .where ( f -> f .bool ()
1200
+ .must ( f .match ().field ( "genre" ).matching ( Genre .SCIENCE_FICTION ) ) // <1>
1201
+ .should ( f .knn ( 10 ).field ( "coverImageEmbeddings" ).matching ( coverImageEmbeddingsVector ) ) // <2>
1202
+ )
1203
+ .fetchHits ( 20 );
1204
+ // end::knn-and-match[]
1205
+ assertThat ( hits )
1206
+ .extracting ( Book ::getId )
1207
+ .containsExactlyInAnyOrder ( BOOK1_ID , BOOK2_ID , BOOK3_ID );
1208
+ } );
1209
+ }
1210
+
1211
+
1212
+ if ( BackendConfiguration .isElasticsearch () ) {
1213
+ withinSearchSession ( searchSession -> {
1214
+ // tag::knn-candidates[]
1215
+ float [] coverImageEmbeddingsVector = /*...*/
1216
+ // end::knn-candidates[]
1217
+ floats ( 128 , 1.0f );
1218
+ // tag::knn-candidates[]
1219
+ List <Book > hits = searchSession .search ( Book .class )
1220
+ .where ( f -> f .extension ( ElasticsearchExtension .get () ) // <1>
1221
+ .knn ( 5 ).field ( "coverImageEmbeddings" ).matching ( coverImageEmbeddingsVector ) // <2>
1222
+ .numberOfCandidates ( 15 ) )// <3>
1223
+ .fetchHits ( 20 );
1224
+ // end::knn-candidates[]
1225
+ assertThat ( hits )
1226
+ .extracting ( Book ::getId )
1227
+ .containsExactlyInAnyOrder ( BOOK1_ID , BOOK2_ID , BOOK3_ID , BOOK4_ID );
1228
+ } );
1229
+ }
1146
1230
}
1147
1231
1148
1232
private MySearchParameters getSearchParameters () {
@@ -1208,6 +1292,7 @@ private void initData() {
1208
1292
book1 .setGenre ( Genre .SCIENCE_FICTION );
1209
1293
book1 .getAuthors ().add ( isaacAsimov );
1210
1294
book1 .setCoverImageEmbeddings ( floats ( 128 , 1.0f ) );
1295
+ book1 .setAlternativeCoverImageEmbeddings ( floats ( 128 , 10.0f ) );
1211
1296
isaacAsimov .getBooks ().add ( book1 );
1212
1297
1213
1298
Book book2 = new Book ();
@@ -1219,6 +1304,7 @@ private void initData() {
1219
1304
book2 .setComment ( "Really liked this one!" );
1220
1305
book2 .getAuthors ().add ( isaacAsimov );
1221
1306
book2 .setCoverImageEmbeddings ( floats ( 128 , 2.0f ) );
1307
+ book2 .setAlternativeCoverImageEmbeddings ( floats ( 128 , 20.0f ) );
1222
1308
isaacAsimov .getBooks ().add ( book2 );
1223
1309
1224
1310
Book book3 = new Book ();
@@ -1229,6 +1315,7 @@ private void initData() {
1229
1315
book3 .setGenre ( Genre .SCIENCE_FICTION );
1230
1316
book3 .getAuthors ().add ( isaacAsimov );
1231
1317
book3 .setCoverImageEmbeddings ( floats ( 128 , 3.0f ) );
1318
+ book3 .setAlternativeCoverImageEmbeddings ( floats ( 128 , 30.0f ) );
1232
1319
isaacAsimov .getBooks ().add ( book3 );
1233
1320
1234
1321
Book book4 = new Book ();
@@ -1239,6 +1326,7 @@ private void initData() {
1239
1326
book4 .setGenre ( Genre .CRIME_FICTION );
1240
1327
book4 .getAuthors ().add ( aLeeMartinez );
1241
1328
book4 .setCoverImageEmbeddings ( floats ( 128 , 4.0f ) );
1329
+ book4 .setAlternativeCoverImageEmbeddings ( floats ( 128 , 40.0f ) );
1242
1330
aLeeMartinez .getBooks ().add ( book3 );
1243
1331
1244
1332
entityManager .persist ( isaacAsimov );
0 commit comments