/*
* 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.engine;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermRangeQuery;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.search.FullTextQuery;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.IndexedEmbedded;
import org.hibernate.search.annotations.NumericField;
import org.hibernate.search.annotations.Store;
import org.hibernate.search.bridge.FieldBridge;
import org.hibernate.search.bridge.builtin.NumericFieldBridge;
import org.hibernate.search.bridge.builtin.ShortBridge;
import org.hibernate.search.bridge.util.impl.BridgeAdaptor;
import org.hibernate.search.bridge.util.impl.NumericFieldUtils;
import org.hibernate.search.engine.ProjectionConstants;
import org.hibernate.search.metadata.FieldDescriptor;
import org.hibernate.search.metadata.FieldSettingsDescriptor.Type;
import org.hibernate.search.metadata.NumericFieldSettingsDescriptor;
import org.hibernate.search.test.SearchTestBase;
import org.hibernate.search.testsupport.TestForIssue;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import static org.fest.assertions.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class NumericFieldTest extends SearchTestBase {
@Override
@Before
public void setUp() throws Exception {
super.setUp();
prepareData();
}
@Override
@After
public void tearDown() throws Exception {
cleanData();
assertTrue( indexIsEmpty() );
super.tearDown();
}
@Test
public void testIndexAndSearchNumericField() {
try ( Session session = openSession() ) {
FullTextSession fts = Search.getFullTextSession( session );
Transaction tx = fts.beginTransaction();
// Range Queries including lower and upper bounds
assertEquals( "Query id ", 3, numericQueryFor( fts, "overriddenFieldName", 1, 3 ).size() );
assertEquals( "Query by double range", 3, numericQueryFor( fts, "latitude", -10d, 10d ).size() );
assertEquals( "Query by short range", 3, numericQueryFor( fts, "importance", (short) 11, (short) 13 ).size() );
assertEquals( "Query by Short range", 3, numericQueryFor( fts, "fallbackImportance", Short.valueOf( "11" ), Short.valueOf( "13" ) ).size() );
assertEquals( "Query by byte range", 3, numericQueryFor( fts, "popularity", (byte) 21, (byte) 23 ).size() );
assertEquals( "Query by Byte range", 3, numericQueryFor( fts, "fallbackPopularity", Byte.valueOf( "21" ), Byte.valueOf( "23" ) ).size() );
assertEquals( "Query by integer range", 4, numericQueryFor( fts, "ranking", 1, 2 ).size() );
assertEquals( "Query by long range", 3, numericQueryFor( fts, "myCounter", 1L, 3L ).size() );
assertEquals( "Query by multi-fields", 2, numericQueryFor( fts, "strMultiple", 0.7d, 0.9d ).size() );
assertEquals( "Query on custom bridge by range", 4, numericQueryFor( fts, "visibleStars", -100L, 500L ).size() );
// Range Queries different bounds
assertEquals( "Query by id excluding upper", 2, numericQueryFor( fts, "overriddenFieldName", 1, 3, true, false ).size() );
assertEquals( "Query by id excluding upper and lower", 1, numericQueryFor( fts, "overriddenFieldName", 1, 3, false, false ).size() );
// Range Query for embedded entities
assertEquals( "Range Query for indexed embedded", 2, numericQueryFor( fts, "country.idh", 0.9, 1d ).size() );
// Range Query across entities
assertEquals( "Range Query across entities", 1, numericQueryFor( fts, "pinPoints.stars", 4, 5 ).size() );
// Exact Matching Queries
assertEquals( "Query id exact", 1, doExactQuery( fts, "overriddenFieldName", 1 ).getId() );
assertEquals( "Query double exact", 2, doExactQuery( fts, "latitude", -10d ).getId() );
assertEquals( "Query short exact", 3, doExactQuery( fts, "importance", 12 ).getId() );
assertEquals( "Query byte exact", 3, doExactQuery( fts, "popularity", 22 ).getId() );
assertEquals( "Query integer exact", 3, doExactQuery( fts, "longitude", -20d ).getId() );
assertEquals( "Query long exact", 4, doExactQuery( fts, "myCounter", 4L ).getId() );
assertEquals( "Query multifield exact", 5, doExactQuery( fts, "strMultiple", 0.1d ).getId() );
assertEquals( "Query on custom bridge exact", 3, doExactQuery( fts, "visibleStars", 1000L ).getId() );
tx.commit();
fts.clear();
// Delete operation on Numeric Id with overriden field name:
tx = fts.beginTransaction();
List<?> allLocations = fts.createCriteria( Location.class ).list();
for ( Object location : allLocations ) {
fts.delete( location );
}
tx.commit();
fts.clear();
tx = fts.beginTransaction();
assertEquals( "Check for deletion on Query", 0, numericQueryFor( fts, "overriddenFieldName", 1, 6 ).size() );
// and now check also for the real index contents:
Query query = NumericFieldUtils.createNumericRangeQuery( "overriddenFieldName", 1, 6, true, true );
FullTextQuery fullTextQuery = fts
.createFullTextQuery( query, Location.class )
.setProjection( ProjectionConstants.ID );
assertEquals( "Check for deletion on index projection", 0, fullTextQuery.list().size() );
tx.commit();
}
}
@TestForIssue(jiraKey = "HSEARCH-1193")
@Test
public void testNumericFieldProjections() {
try ( Session session = openSession() ) {
FullTextSession fullTextSession = Search.getFullTextSession( session );
Transaction tx = fullTextSession.beginTransaction();
Query latitudeQuery = NumericFieldUtils.createNumericRangeQuery( "latitude", -20d, -20d, true, true );
List<?> list = fullTextSession.createFullTextQuery( latitudeQuery, Location.class )
.setProjection( "latitude" )
.list();
Assert.assertEquals( 1, list.size() );
Object[] firstProjection = (Object[]) list.get( 0 );
Assert.assertEquals( 1, firstProjection.length );
Assert.assertEquals( -20d, firstProjection[0] );
List<?> listAgain = fullTextSession.createFullTextQuery( latitudeQuery, Location.class )
.setProjection( "coordinatePair_x", "coordinatePair_y", "importance", "popularity" )
.list();
Assert.assertEquals( 1, listAgain.size() );
Object[] secondProjection = (Object[]) listAgain.get( 0 );
Assert.assertEquals( 4, secondProjection.length );
Assert.assertEquals( 1d, secondProjection[0] );
Assert.assertEquals( 2d, secondProjection[1] );
Assert.assertEquals( (short) 10, secondProjection[2] );
Assert.assertEquals( (byte) 20, secondProjection[3] );
tx.commit();
}
}
@Test
@TestForIssue(jiraKey = "HSEARCH-997")
public void testShortDocumentIdExplicitlyMappedAsNumericField() {
try ( Session session = openSession() ) {
FullTextSession fullTextSession = Search.getFullTextSession( session );
Transaction tx = fullTextSession.beginTransaction();
Query query = NumericFieldUtils.createNumericRangeQuery( "myId", (short) 1, (short) 1, true, true );
@SuppressWarnings("unchecked")
List<Coordinate> list = fullTextSession.createFullTextQuery( query, Coordinate.class )
.list();
Assert.assertEquals( 1, list.size() );
Assert.assertEquals( (short) 1, list.iterator().next().getId() );
tx.commit();
}
}
@Test
@TestForIssue(jiraKey = "HSEARCH-997")
public void testByteDocumentIdExplicitlyMappedAsNumericField() {
try ( Session session = openSession() ) {
FullTextSession fullTextSession = Search.getFullTextSession( session );
Transaction tx = fullTextSession.beginTransaction();
Query query = NumericFieldUtils.createNumericRangeQuery( "myId", (byte) 1, (byte) 1, true, true );
@SuppressWarnings("unchecked")
List<PointOfInterest> list = fullTextSession.createFullTextQuery( query, PointOfInterest.class )
.list();
Assert.assertEquals( 1, list.size() );
Assert.assertEquals( (byte) 1, list.iterator().next().getId() );
tx.commit();
}
}
@Test
@TestForIssue(jiraKey = "HSEARCH-997")
public void testByteDocumentIdMappedAsStringFieldByDefault() {
try ( Session session = openSession() ) {
FullTextSession fullTextSession = Search.getFullTextSession( session );
Transaction tx = fullTextSession.beginTransaction();
Query query = TermRangeQuery.newStringRange( "id", "1", "1", true, true );
@SuppressWarnings("unchecked")
List<Position> list = fullTextSession.createFullTextQuery( query, Position.class )
.list();
Assert.assertEquals( 1, list.size() );
Assert.assertEquals( (byte) 1, list.iterator().next().getId() );
tx.commit();
}
}
@Test
@TestForIssue(jiraKey = "HSEARCH-1987")
public void testOneOfSeveralFieldsIsNumeric() {
assertEquals(
NumericFieldBridge.SHORT_FIELD_BRIDGE,
getUnwrappedBridge( TouristAttraction.class , "scoreNumeric", NumericFieldBridge.class )
);
assertNotNull(
getUnwrappedBridge( TouristAttraction.class , "scoreString", ShortBridge.class )
);
}
private Object getUnwrappedBridge(Class<?> clazz, String string, Class<?> expectedBridgeClass) {
FieldBridge bridge = getExtendedSearchIntegrator().getIndexBinding( clazz ).getDocumentBuilder()
.getMetadata().getDocumentFieldMetadataFor( string ).getFieldBridge();
return unwrapBridge( bridge, expectedBridgeClass );
}
private Object unwrapBridge(Object bridge, Class<?> expectedBridgeClass) {
if ( bridge instanceof BridgeAdaptor ) {
return ((BridgeAdaptor) bridge).unwrap( expectedBridgeClass );
}
else {
return bridge;
}
}
@Test
@TestForIssue(jiraKey = "HSEARCH-1987")
public void testSomeOfSeveralFieldsAreNumeric() {
try ( Session session = openSession() ) {
FullTextSession fullTextSession = Search.getFullTextSession( session );
Set<FieldDescriptor> fields = fullTextSession.getSearchFactory()
.getIndexedTypeDescriptor( TouristAttraction.class )
.getProperty( "rating" )
.getIndexedFields();
assertThat( fields ).onProperty( "name" )
.containsOnly( "rating", "ratingNumericPrecision1", "ratingNumericPrecision2" );
for ( FieldDescriptor field : fields ) {
if ( "ratingNumericPrecision1".equals( field.getName() ) ) {
assertThat( field.getType() ).isEqualTo( Type.NUMERIC );
assertThat( field.as( NumericFieldSettingsDescriptor.class ).precisionStep() ).isEqualTo( 1 );
}
else if ( "ratingNumericPrecision2".equals( field.getName() ) ) {
assertThat( field.getType() ).isEqualTo( Type.NUMERIC );
assertThat( field.as( NumericFieldSettingsDescriptor.class ).precisionStep() ).isEqualTo( 2 );
}
else {
assertThat( field.getType() ).isEqualTo( Type.BASIC );
}
}
}
}
@Test
@TestForIssue(jiraKey = "HSEARCH-1987")
public void testNumericMappingOfEmbeddedFields() {
assertEquals(
NumericFieldBridge.INT_FIELD_BRIDGE,
getUnwrappedBridge( ScoreBoard.class , "score_id", NumericFieldBridge.class )
);
assertEquals(
NumericFieldBridge.INT_FIELD_BRIDGE,
getUnwrappedBridge( ScoreBoard.class , "score_beta", NumericFieldBridge.class )
);
}
private boolean indexIsEmpty() {
int numDocsLocation = countSizeForType( Location.class );
int numDocsPinPoint = countSizeForType( PinPoint.class );
return numDocsLocation == 0 && numDocsPinPoint == 0;
}
private int countSizeForType(Class<?> type) {
return getNumberOfDocumentsInIndex( type );
}
@Override
public Class<?>[] getAnnotatedClasses() {
return new Class<?>[]{ PinPoint.class, Location.class, Coordinate.class,
PointOfInterest.class, Position.class, TouristAttraction.class, ScoreBoard.class, Score.class };
}
private Location doExactQuery(FullTextSession fullTextSession, String fieldName, Object value) {
Query matchQuery = NumericFieldUtils.createExactMatchQuery( fieldName, value );
return (Location) fullTextSession.createFullTextQuery( matchQuery, Location.class ).list().get( 0 );
}
private List<?> numericQueryFor(FullTextSession fullTextSession, String fieldName, Object from, Object to) {
Query query = NumericFieldUtils.createNumericRangeQuery( fieldName, from, to, true, true );
return fullTextSession.createFullTextQuery( query, Location.class ).list();
}
private List<?> numericQueryFor(FullTextSession fullTextSession, String fieldName, Object from, Object to, boolean includeLower, boolean includeUpper) {
Query query = NumericFieldUtils.createNumericRangeQuery( fieldName, from, to, includeLower, includeUpper );
return fullTextSession.createFullTextQuery( query, Location.class ).list();
}
private void prepareData() {
try ( Session session = openSession() ) {
FullTextSession fullTextSession = Search.getFullTextSession( session );
Transaction tx = fullTextSession.beginTransaction();
Location loc1 = new Location( 1, 1L, -20d, -40d, 1, "Random text", 1.5d, countryFor( "England", 0.947 ), BigDecimal.ONE, (short) 10, (byte) 20 );
loc1.addPinPoints( new PinPoint( 1, 4, loc1 ), new PinPoint( 2, 5, loc1 ) );
Location loc2 = new Location( 2, 2L, -10d, -30d, 1, "Some text", 0.786d, countryFor( "Italy", 0.951 ), BigDecimal.ONE, (short) 11, (byte) 21 );
loc2.addPinPoints( new PinPoint( 3, 1, loc2 ), new PinPoint( 4, 2, loc2 ) );
Location loc3 = new Location( 3, 3L, 0d, -20d, 1, "A text", 0.86d, countryFor( "Brazil", 0.813 ), BigDecimal.TEN, (short) 12, (byte) 22 );
Location loc4 = new Location( 4, 4L, 10d, 0d, 2, "Any text", 0.99d, countryFor( "France", 0.872 ), BigDecimal.ONE, (short) 13, (byte) 23 );
Location loc5 = new Location( 5, 5L, 20d, 20d, 3, "Random text", 0.1d, countryFor( "India", 0.612 ), BigDecimal.ONE, (short) 14, (byte) 24 );
fullTextSession.save( loc1 );
fullTextSession.save( loc2 );
fullTextSession.save( loc3 );
fullTextSession.save( loc4 );
fullTextSession.save( loc5 );
Coordinate coordinate1 = new Coordinate( (short) 1, -20D, 20D );
Coordinate coordinate2 = new Coordinate( (short) 2, -30D, 30D );
fullTextSession.save( coordinate1 );
fullTextSession.save( coordinate2 );
PointOfInterest poi1 = new PointOfInterest( (byte) 1, -20D, 20D );
PointOfInterest poi2 = new PointOfInterest( (byte) 2, -30D, 30D );
fullTextSession.save( poi1 );
fullTextSession.save( poi2 );
Position position1 = new Position( (byte) 1, -20D, 20D );
Position position2 = new Position( (byte) 2, -30D, 30D );
fullTextSession.save( position1 );
fullTextSession.save( position2 );
TouristAttraction attraction = new TouristAttraction( 1, (short) 23, (short) 46L );
fullTextSession.save( attraction );
Score score1 = new Score();
score1.id = 1;
score1.subscore = 100;
fullTextSession.save( score1 );
ScoreBoard scoreboard = new ScoreBoard();
scoreboard.id = 1l;
scoreboard.scores.add( score1 );
fullTextSession.save( scoreboard );
tx.commit();
}
}
private Country countryFor(String name, double idh) {
return new Country( name, idh );
}
@SuppressWarnings("unchecked")
private void cleanData() {
try ( Session session = openSession() ) {
FullTextSession fullTextSession = Search.getFullTextSession( session );
Transaction tx = fullTextSession.beginTransaction();
List<Location> locations = fullTextSession.createCriteria( Location.class ).list();
for ( Location location : locations ) {
fullTextSession.delete( location );
}
List<Coordinate> coordinates = fullTextSession.createCriteria( Coordinate.class ).list();
for ( Coordinate coordinate : coordinates ) {
fullTextSession.delete( coordinate );
}
List<PointOfInterest> pois = fullTextSession.createCriteria( PointOfInterest.class ).list();
for ( PointOfInterest poi : pois ) {
fullTextSession.delete( poi );
}
List<Position> positions = fullTextSession.createCriteria( Position.class ).list();
for ( Position position : positions ) {
fullTextSession.delete( position );
}
List<TouristAttraction> attractions = fullTextSession.createCriteria( TouristAttraction.class ).list();
for ( TouristAttraction attraction : attractions ) {
fullTextSession.delete( attraction );
}
List<ScoreBoard> scoreboards = fullTextSession.createCriteria( ScoreBoard.class ).list();
for ( ScoreBoard scoreboard : scoreboards ) {
fullTextSession.delete( scoreboard );
}
List<Score> scores = fullTextSession.createCriteria( Score.class ).list();
for ( Score score : scores ) {
fullTextSession.delete( score );
}
tx.commit();
}
}
@Indexed @Entity
static class ScoreBoard {
@Id
Long id;
@IndexedEmbedded(includeEmbeddedObjectId = true, prefix = "score_")
@OneToMany
Set<Score> scores = new HashSet<Score>();
}
@Indexed @Entity
static class Score {
@Id
@NumericField
Integer id;
@Field(name = "beta", store = Store.YES)
Integer subscore;
}
}