/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.test.dsl;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.lucene.document.Document;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.FieldBridge;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.NumericField;
import org.hibernate.search.backend.spi.Work;
import org.hibernate.search.backend.spi.WorkType;
import org.hibernate.search.bridge.LuceneOptions;
import org.hibernate.search.bridge.StringBridge;
import org.hibernate.search.cfg.Environment;
import org.hibernate.search.engine.integration.impl.ExtendedSearchIntegrator;
import org.hibernate.search.query.dsl.QueryBuilder;
import org.hibernate.search.query.engine.spi.EntityInfo;
import org.hibernate.search.query.engine.spi.HSQuery;
import org.hibernate.search.testsupport.TestForIssue;
import org.hibernate.search.testsupport.junit.SearchFactoryHolder;
import org.hibernate.search.testsupport.junit.SkipOnElasticsearch;
import org.hibernate.search.testsupport.setup.TransactionContextForTest;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
/**
* @author Emmanuel Bernard
* @author Yoann Rodiere
*/
@TestForIssue(jiraKey = "HSEARCH-2588")
@Category(SkipOnElasticsearch.class) // Elasticsearch doesn't support non-metadata-providing field bridges
public class LuceneSortDSLTest {
@Rule
public SearchFactoryHolder sfHolder = new SearchFactoryHolder( IndexedEntry.class )
.withProperty( Environment.INDEX_UNINVERTING_ALLOWED, "true" );
@Before
public void prepareTestData() {
IndexedEntry entry0 = new IndexedEntry( 0 )
.setUniqueDoubleField( 2d );
IndexedEntry entry1 = new IndexedEntry( 1 )
.setUniqueDoubleField( 1d );
IndexedEntry entry2 = new IndexedEntry( 2 );
IndexedEntry entry3 = new IndexedEntry( 3 )
.setUniqueDoubleField( 3d );
storeData( entry0 );
storeData( entry1 );
storeData( entry2 );
storeData( entry3 );
}
private QueryBuilder builder() {
return sfHolder.getSearchFactory().buildQueryBuilder().forEntity( IndexedEntry.class ).get();
}
@Test
public void singleField_numericFieldBridge_nonMetadataProviding() throws Exception {
Query query = builder().all().createQuery();
// Missing value is not provided; the missing values should be considered as 0
Sort sort = builder().sort()
.byField( "nonMetadataProvidingFieldBridgedNumericField", SortField.Type.DOUBLE )
.createSort();
assertThat(
query( query, sort ),
returnsIDsInOrder( 2, 1, 0, 3 )
);
sort = builder().sort()
.byField( "nonMetadataProvidingFieldBridgedNumericField", SortField.Type.DOUBLE )
.asc()
.createSort();
assertThat(
query( query, sort ),
returnsIDsInOrder( 2, 1, 0, 3 )
);
sort = builder().sort()
.byField( "nonMetadataProvidingFieldBridgedNumericField", SortField.Type.DOUBLE )
.desc()
.createSort();
assertThat(
query( query, sort ),
returnsIDsInOrder( 3, 0, 1, 2 )
);
}
@Test
public void singleField_numericFieldBridge_nonMetadataProviding_missingValue_use() throws Exception {
Query query = builder().all().createQuery();
Sort sort = builder().sort()
.byField( "nonMetadataProvidingFieldBridgedNumericField", SortField.Type.DOUBLE )
.onMissingValue().use( 1.5d )
.createSort();
assertThat(
query( query, sort ),
returnsIDsInOrder( 1, 2, 0, 3 )
);
sort = builder().sort()
.byField( "nonMetadataProvidingFieldBridgedNumericField", SortField.Type.DOUBLE )
.asc()
.onMissingValue().use( 1.5d )
.createSort();
assertThat(
query( query, sort ),
returnsIDsInOrder( 1, 2, 0, 3 )
);
sort = builder().sort()
.byField( "nonMetadataProvidingFieldBridgedNumericField", SortField.Type.DOUBLE )
.desc()
.onMissingValue().use( 1.5d )
.createSort();
assertThat(
query( query, sort ),
returnsIDsInOrder( 3, 0, 2, 1 )
);
}
@Test(expected = ClassCastException.class)
public void singleField_numericFieldBridge_nonMetadataProviding_missingValue_use_nonRaw() throws Exception {
Query query = builder().all().createQuery();
Sort sort = builder().sort()
.byField( "nonMetadataProvidingFieldBridgedNumericField", SortField.Type.DOUBLE )
.onMissingValue().use( new WrappedDoubleValue( 1.5d ) )
.createSort();
query( query, sort );
}
private Matcher<List<EntityInfo>> returnsIDsInOrder(Integer ... idsInOrder) {
final List<Integer> idsInOrderList = Arrays.asList( idsInOrder );
return new TypeSafeMatcher<List<EntityInfo>>() {
@Override
public void describeTo(Description description) {
description.appendText( "a list containing exactly (and in the same order) " )
.appendValue( idsInOrderList );
}
@Override
protected void describeMismatchSafely(List<EntityInfo> item, Description mismatchDescription) {
mismatchDescription.appendText( "was " ).appendValue( toIds( item ) );
}
private List<Object> toIds(List<EntityInfo> entityInfos) {
List<Object> result = new ArrayList<>();
for ( EntityInfo entityInfo : entityInfos ) {
result.add( entityInfo.getProjection()[0] );
}
return result;
}
@Override
protected boolean matchesSafely(List<EntityInfo> actual) {
return idsInOrderList.equals( toIds( actual ) );
}
};
}
private List<EntityInfo> query(Query luceneQuery, Sort sort) {
ExtendedSearchIntegrator sf = sfHolder.getSearchFactory();
HSQuery hsQuery = sf.createHSQuery( luceneQuery, IndexedEntry.class );
return hsQuery
.projection( "id" )
.sort( sort )
.queryEntityInfos();
}
private void storeData(IndexedEntry entry) {
Work work = new Work( entry, entry.id, WorkType.ADD, false );
TransactionContextForTest tc = new TransactionContextForTest();
sfHolder.getSearchFactory().getWorker().performWork( work, tc );
tc.end();
}
public static class WrappedDoubleValue {
final Double value;
public WrappedDoubleValue(Double value) {
super();
this.value = value;
}
}
public static class WrappedDoubleValueNonMetadataProvidingFieldBridge implements org.hibernate.search.bridge.FieldBridge, StringBridge {
@Override
public String objectToString(Object object) {
if ( object == null ) {
return null;
}
return object.toString();
}
@Override
public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
if ( value == null ) {
return;
}
Double doubleValue = ((WrappedDoubleValue) value).value;
if ( doubleValue == null ) {
return;
}
luceneOptions.addNumericFieldToDocument( name, doubleValue, document );
luceneOptions.addNumericDocValuesFieldToDocument( name, doubleValue, document );
}
}
@Indexed
public static class IndexedEntry {
@DocumentId
int id;
@Field(name = "nonMetadataProvidingFieldBridgedNumericField", bridge = @FieldBridge(impl = WrappedDoubleValueNonMetadataProvidingFieldBridge.class))
@NumericField(forField = "nonMetadataProvidingFieldBridgedNumericField")
WrappedDoubleValue fieldBridgedNumericField;
public IndexedEntry() {
}
public IndexedEntry(int id) {
super();
this.id = id;
}
public IndexedEntry setUniqueDoubleField(Double uniqueDoubleField) {
this.fieldBridgedNumericField = new WrappedDoubleValue( uniqueDoubleField );
return this;
}
}
}