/* * 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.query.facet; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.List; import java.util.Locale; import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.search.FullTextQuery; import org.hibernate.search.exception.SearchException; import org.hibernate.search.query.engine.spi.FacetManager; import org.hibernate.search.query.facet.Facet; import org.hibernate.search.query.facet.FacetSortOrder; import org.hibernate.search.query.facet.FacetingRequest; import org.hibernate.testing.TestForIssue; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** * @author Hardy Ferentschik */ public class RangeFacetingTest extends AbstractFacetTest { private static final String indexFieldName = "price"; private static final String priceRange = "priceRange"; @Test public void testRangeQueryForInteger() { FacetingRequest rangeRequest = queryBuilder( Cd.class ).facet() .name( priceRange ) .onField( indexFieldName ) .range() .from( 0 ).to( 1000 ) .from( 1001 ).to( 1500 ) .from( 1501 ).to( 3000 ) .from( 3001 ).to( 8000 ) .includeZeroCounts( true ) .createFacetingRequest(); FullTextQuery query = createMatchAllQuery( Cd.class ); FacetManager facetManager = query.getFacetManager(); facetManager.enableFaceting( rangeRequest ); List<Facet> facets = facetManager.getFacets( priceRange ); assertFacetCounts( facets, new int[] { 5, 3, 2, 0 } ); } @Test public void testRangeBelow() { FacetingRequest rangeRequest = queryBuilder( Cd.class ).facet() .name( priceRange ) .onField( indexFieldName ) .range() .below( 1500 ) .createFacetingRequest(); FullTextQuery query = createMatchAllQuery( Cd.class ); FacetManager facetManager = query.getFacetManager(); facetManager.enableFaceting( rangeRequest ); List<Facet> facets = facetManager.getFacets( priceRange ); assertFacetCounts( facets, new int[] { 5 } ); } @Test public void testRangeBelowExcludeLimit() { FacetingRequest rangeRequest = queryBuilder( Cd.class ).facet() .name( priceRange ) .onField( indexFieldName ) .range() .below( 1500 ).excludeLimit() .createFacetingRequest(); FullTextQuery query = createMatchAllQuery( Cd.class ); FacetManager facetManager = query.getFacetManager(); facetManager.enableFaceting( rangeRequest ); List<Facet> facets = facetManager.getFacets( priceRange ); assertFacetCounts( facets, new int[] { 2 } ); } @Test public void testRangeAbove() { FacetingRequest rangeRequest = queryBuilder( Cd.class ).facet() .name( priceRange ) .onField( indexFieldName ) .range() .above( 1500 ) .createFacetingRequest(); FullTextQuery query = createMatchAllQuery( Cd.class ); FacetManager facetManager = query.getFacetManager(); facetManager.enableFaceting( rangeRequest ); List<Facet> facets = facetManager.getFacets( priceRange ); assertFacetCounts( facets, new int[] { 8 } ); } @Test public void testRangeAboveExcludeLimit() { FacetingRequest rangeRequest = queryBuilder( Cd.class ).facet() .name( priceRange ) .onField( indexFieldName ) .range() .above( 1500 ).excludeLimit() .createFacetingRequest(); FullTextQuery query = createMatchAllQuery( Cd.class ); FacetManager facetManager = query.getFacetManager(); facetManager.enableFaceting( rangeRequest ); List<Facet> facets = facetManager.getFacets( priceRange ); assertFacetCounts( facets, new int[] { 5 } ); } @Test public void testRangeAboveBelow() { FacetingRequest rangeRequest = queryBuilder( Cd.class ).facet() .name( priceRange ) .onField( indexFieldName ) .range() .below( 1500 ) .above( 1500 ).excludeLimit() .createFacetingRequest(); FullTextQuery query = createMatchAllQuery( Cd.class ); FacetManager facetManager = query.getFacetManager(); facetManager.enableFaceting( rangeRequest ); List<Facet> facets = facetManager.getFacets( priceRange ); assertFacetCounts( facets, new int[] { 5, 5 } ); } @Test public void testRangeBelowMiddleAbove() { final String facetingName = "cdPriceFaceting"; FacetingRequest rangeRequest = queryBuilder( Cd.class ).facet() .name( facetingName ) .onField( indexFieldName ) .range() .below( 1000 ) .from( 1001 ).to( 1500 ) .above( 1501 ) .createFacetingRequest(); FullTextQuery query = createMatchAllQuery( Cd.class ); query.getFacetManager().enableFaceting( rangeRequest ); List<Facet> facets = query.getFacetManager().getFacets( facetingName ); assertFacetCounts( facets, new int[] { 5, 3, 2 } ); } @Test public void testRangeWithExcludeLimitsAtEachLevel() { final String facetingName = "cdPriceFaceting"; FacetingRequest rangeRequest = queryBuilder( Cd.class ).facet() .name( facetingName ) .onField( indexFieldName ) .range() .below( 1000 ).excludeLimit() .from( 1000 ).to( 1500 ).excludeLimit() .from( 1500 ).to( 2000 ).excludeLimit() .above( 2000 ) .orderedBy( FacetSortOrder.RANGE_DEFINITION_ORDER ) .includeZeroCounts( true ) .createFacetingRequest(); FullTextQuery query = createMatchAllQuery( Cd.class ); query.getFacetManager().enableFaceting( rangeRequest ); List<Facet> facets = query.getFacetManager().getFacets( facetingName ); assertFacetCounts( facets, new int[] { 2, 0, 6, 2 } ); rangeRequest = queryBuilder( Cd.class ).facet() .name( facetingName ) .onField( indexFieldName ) .range() .below( 1000 ) .from( 1000 ).excludeLimit().to( 1500 ) .from( 1500 ).excludeLimit().to( 2000 ) .above( 2000 ).excludeLimit() .orderedBy( FacetSortOrder.RANGE_DEFINITION_ORDER ) .createFacetingRequest(); query = createMatchAllQuery( Cd.class ); query.getFacetManager().enableFaceting( rangeRequest ); facets = query.getFacetManager().getFacets( facetingName ); assertFacetCounts( facets, new int[] { 2, 3, 4, 1 } ); } @Test @TestForIssue(jiraKey = "HSEARCH-770") public void testRangeBelowWithFacetSelection() { final String facetingName = "truckHorsePowerFaceting"; FacetingRequest rangeRequest = queryBuilder( Truck.class ).facet() .name( facetingName ) .onField( "horsePower" ) .range() .below( 1000 ) .createFacetingRequest(); FullTextQuery query = createMatchAllQuery( Truck.class ); FacetManager facetManager = query.getFacetManager(); query.getFacetManager().enableFaceting( rangeRequest ); List<Facet> facets = facetManager.getFacets( facetingName ); // OK facets = facetManager.getFacets( facetingName ); // Still OK assertFacetCounts( facets, new int[] { 4 } ); facetManager.getFacetGroup( facetingName ).selectFacets( facets.get( 0 ) ); // narrow search on facet facets = facetManager.getFacets( facetingName ); // Exception... assertFacetCounts( facets, new int[] { 4 } ); } @Test public void testRangeQueryForDoubleWithZeroCount() { FacetingRequest rangeRequest = queryBuilder( Fruit.class ).facet() .name( priceRange ) .onField( indexFieldName ) .range() .from( 0.00 ).to( 1.00 ) .from( 1.01 ).to( 1.50 ) .from( 1.51 ).to( 3.00 ) .from( 4.00 ).to( 5.00 ) .includeZeroCounts( true ) .createFacetingRequest(); FullTextQuery query = createMatchAllQuery( Fruit.class ); FacetManager facetManager = query.getFacetManager(); facetManager.enableFaceting( rangeRequest ); List<Facet> facets = facetManager.getFacets( priceRange ); assertFacetCounts( facets, new int[] { 5, 3, 2, 0 } ); } @Test public void testRangeQueryForDoubleWithoutZeroCount() { FacetingRequest rangeRequest = queryBuilder( Fruit.class ).facet() .name( priceRange ) .onField( indexFieldName ) .range() .from( 0.00 ).to( 1.00 ) .from( 1.01 ).to( 1.50 ) .from( 1.51 ).to( 3.00 ) .from( 4.00 ).to( 5.00 ) .includeZeroCounts( false ) .orderedBy( FacetSortOrder.COUNT_ASC ) .createFacetingRequest(); FullTextQuery query = createMatchAllQuery( Fruit.class ); FacetManager facetManager = query.getFacetManager(); facetManager.enableFaceting( rangeRequest ); List<Facet> facets = query.getFacetManager().getFacets( priceRange ); assertFacetCounts( facets, new int[] { 2, 3, 5 } ); assertEquals( "[0.0, 1.0]", facets.get( 0 ).getValue() ); assertEquals( "[1.01, 1.5]", facets.get( 1 ).getValue() ); assertEquals( "[1.51, 3.0]", facets.get( 2 ).getValue() ); } @Test public void testRangeQueryRangeDefOrderHigherMaxCount() { FacetingRequest rangeRequest = queryBuilder( Fruit.class ).facet() .name( priceRange ) .onField( indexFieldName ) .range() .from( 0.00 ).to( 1.00 ) .from( 1.01 ).to( 1.50 ) .from( 1.51 ).to( 3.00 ) .from( 4.00 ).to( 5.00 ) .includeZeroCounts( false ) .orderedBy( FacetSortOrder.RANGE_DEFINITION_ORDER ) .maxFacetCount( 5 ) .createFacetingRequest(); FullTextQuery query = createMatchAllQuery( Fruit.class ); FacetManager facetManager = query.getFacetManager(); facetManager.enableFaceting( rangeRequest ); List<Facet> facets = query.getFacetManager().getFacets( priceRange ); assertFacetCounts( facets, new int[] { 2, 3, 5 } ); assertEquals( "[0.0, 1.0]", facets.get( 0 ).getValue() ); assertEquals( "[1.01, 1.5]", facets.get( 1 ).getValue() ); assertEquals( "[1.51, 3.0]", facets.get( 2 ).getValue() ); } @Test public void testDateRangeFaceting() throws Exception { final String facetingName = "albumYearFaceting"; final String fieldName = "releaseYear"; final DateFormat formatter = new SimpleDateFormat( "yyyy", Locale.ROOT ); FacetingRequest rangeRequest = queryBuilder( Cd.class ).facet() .name( facetingName ) .onField( fieldName ) .range() .below( formatter.parse( "1970" ) ).excludeLimit() .from( formatter.parse( "1970" ) ).to( formatter.parse( "1979" ) ) .from( formatter.parse( "1980" ) ).to( formatter.parse( "1989" ) ) .from( formatter.parse( "1990" ) ).to( formatter.parse( "1999" ) ) .above( formatter.parse( "2000" ) ).excludeLimit() .orderedBy( FacetSortOrder.RANGE_DEFINITION_ORDER ) .createFacetingRequest(); FullTextQuery query = createMatchAllQuery( Cd.class ); FacetManager facetManager = query.getFacetManager(); facetManager.enableFaceting( rangeRequest ); List<Facet> facets = facetManager.getFacets( facetingName ); assertFacetCounts( facets, new int[] { 1, 2, 2, 5 } ); facetManager.getFacetGroup( facetingName ).selectFacets( facets.get( 3 ) ); facets = facetManager.getFacets( facetingName ); assertFacetCounts( facets, new int[] { 5 } ); } @Test public void testRangeQueryWithUnsupportedType() { try { queryBuilder( Cd.class ).facet() .name( priceRange ) .onField( indexFieldName ) .range() .from( new Object() ).to( new Object() ) .createFacetingRequest(); fail( "Unsupported range faceting type" ); } catch (SearchException e) { assertTrue( "Unexpected error message: " + e.getMessage(), e.getMessage().startsWith( "HSEARCH000269" ) ); } } @Test public void testRangeQueryWithNullToAndFrom() { try { queryBuilder( Cd.class ).facet() .name( priceRange ) .onField( indexFieldName ) .range() .from( null ).to( null ) .createFacetingRequest(); fail( "Unsupported range faceting type" ); } catch (SearchException e) { assertTrue( "Unexpected error message: " + e.getMessage(), e.getMessage().startsWith( "HSEARCH000270" ) ); } } @Test public void testUnsupportedRangeParameterTypeThrowsException() { FacetingRequest rangeRequest = queryBuilder( Fruit.class ).facet() .name( priceRange ) .onField( indexFieldName ) .range() .from( "0.00" ).to( "1.00" ) .createFacetingRequest(); FullTextQuery query = createMatchAllQuery( Fruit.class ); FacetManager facetManager = query.getFacetManager(); facetManager.enableFaceting( rangeRequest ); try { query.getFacetManager().getFacets( priceRange ); fail( "Trying to so a range query using string as parameter should fail." ); } catch (SearchException e) { assertTrue( "Unexpected error message: " + e.getMessage(), e.getMessage().startsWith( "HSEARCH000266" ) ); } } @Override public void loadTestData(Session session) { Transaction tx = session.beginTransaction(); for ( int i = 0; i < albums.length; i++ ) { Cd cd = new Cd( albums[i], albumPrices[i], releaseDates[i] ); session.save( cd ); } for ( int i = 0; i < fruits.length; i++ ) { Fruit fruit = new Fruit( fruits[i], fruitPrices[i] ); session.save( fruit ); } for ( Integer horsePower : horsePowers ) { Truck truck = new Truck( horsePower ); session.save( truck ); } tx.commit(); session.clear(); } @Override public Class<?>[] getAnnotatedClasses() { return new Class[] { Cd.class, Fruit.class, Truck.class }; } }