/* * Hibernate, Relational Persistence for Idiomatic Java * * Copyright (c) 2010, Red Hat, Inc. and/or its affiliates or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are * distributed under license by Red Hat, Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package org.hibernate.search.test.query.dsl; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.TimeZone; import org.apache.lucene.document.DateTools; import org.apache.lucene.search.NumericRangeQuery; import org.apache.lucene.search.Query; import org.apache.solr.analysis.LowerCaseFilterFactory; import org.apache.solr.analysis.NGramFilterFactory; import org.apache.solr.analysis.SnowballPorterFilterFactory; import org.apache.solr.analysis.StandardFilterFactory; import org.apache.solr.analysis.StandardTokenizerFactory; import org.apache.solr.analysis.StopFilterFactory; import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import org.hibernate.search.Environment; import org.hibernate.search.FullTextQuery; import org.hibernate.search.FullTextSession; import org.hibernate.search.Search; import org.hibernate.search.annotations.Factory; import org.hibernate.search.cfg.SearchMapping; import org.hibernate.search.query.dsl.QueryBuilder; import org.hibernate.search.query.dsl.Unit; import org.hibernate.search.spatial.Coordinates; import org.hibernate.search.spatial.impl.Point; import org.hibernate.search.test.SearchTestCase; import org.hibernate.search.test.util.TestForIssue; /** * @author Emmanuel Bernard * @author Hardy Ferentschik */ //DO NOT AUTO INDENT THIS FILE. //MY DSL IS BEAUTIFUL, DUMP INDENTATION IS SCREWING IT UP public class DSLTest extends SearchTestCase { private final Calendar calendar = Calendar.getInstance(); private FullTextSession fullTextSession; private Date january; private Date february; private Date march; public void setUp() throws Exception { super.setUp(); Session session = openSession(); fullTextSession = Search.getFullTextSession( session ); indexTestData(); } public void tearDown() throws Exception { cleanUpTestData(); super.tearDown(); } public void testUseOfFieldBridge() throws Exception { Transaction transaction = fullTextSession.beginTransaction(); final QueryBuilder monthQb = fullTextSession.getSearchFactory() .buildQueryBuilder().forEntity( Month.class ).get(); Query query = monthQb.keyword().onField( "monthValue" ).matching( 2 ).createQuery(); assertEquals( 1, fullTextSession.createFullTextQuery( query, Month.class ).getResultSize() ); query = monthQb.keyword() .onField( "monthValue" ) .ignoreFieldBridge() .matching( "2" ) .createQuery(); assertEquals( 1, fullTextSession.createFullTextQuery( query, Month.class ).getResultSize() ); transaction.commit(); } public void testTermQueryOnAnalyzer() throws Exception { Transaction transaction = fullTextSession.beginTransaction(); final QueryBuilder monthQb = fullTextSession.getSearchFactory() .buildQueryBuilder().forEntity( Month.class ).get(); //regular term query Query query = monthQb.keyword().onField( "mythology" ).matching( "cold" ).createQuery(); assertEquals( 0, fullTextSession.createFullTextQuery( query, Month.class ).getResultSize() ); //term query based on several words query = monthQb.keyword().onField( "mythology" ).matching( "colder darker" ).createQuery(); assertEquals( 1, fullTextSession.createFullTextQuery( query, Month.class ).getResultSize() ); //term query applying the analyzer and generating one term per word query = monthQb.keyword().onField( "mythology_stem" ).matching( "snowboard" ).createQuery(); assertEquals( 1, fullTextSession.createFullTextQuery( query, Month.class ).getResultSize() ); //term query applying the analyzer and generating several terms per word query = monthQb.keyword().onField( "mythology_ngram" ).matching( "snobored" ).createQuery(); assertEquals( 1, fullTextSession.createFullTextQuery( query, Month.class ).getResultSize() ); //term query not using analyzers query = monthQb.keyword().onField( "mythology" ).ignoreAnalyzer().matching( "Month" ).createQuery(); assertEquals( 0, fullTextSession.createFullTextQuery( query, Month.class ).getResultSize() ); transaction.commit(); } public void testFuzzyAndWildcardQuery() throws Exception { Transaction transaction = fullTextSession.beginTransaction(); final QueryBuilder monthQb = fullTextSession.getSearchFactory() .buildQueryBuilder().forEntity( Month.class ).get(); //fuzzy search with custom threshold and prefix Query query = monthQb .keyword() .fuzzy() .withThreshold( .8f ) .withPrefixLength( 1 ) .onField( "mythology" ) .matching( "calder" ) .createQuery(); assertEquals( 1, fullTextSession.createFullTextQuery( query, Month.class ).getResultSize() ); //fuzzy search on multiple fields query = monthQb .keyword() .fuzzy() .withThreshold( .8f ) .withPrefixLength( 1 ) .onFields( "mythology", "history" ) .matching( "showboarding" ) .createQuery(); assertEquals( 2, fullTextSession.createFullTextQuery( query, Month.class ).getResultSize() ); //wildcard query query = monthQb .keyword() .wildcard() .onField( "mythology" ) .matching( "mon*" ) .createQuery(); assertEquals( 3, fullTextSession.createFullTextQuery( query, Month.class ).getResultSize() ); transaction.commit(); } @SuppressWarnings("unchecked") public void testQueryCustomization() throws Exception { Transaction transaction = fullTextSession.beginTransaction(); final QueryBuilder monthQb = fullTextSession.getSearchFactory() .buildQueryBuilder().forEntity( Month.class ).get(); //combined query, January and february both contain whitening but February in a longer text Query query = monthQb .bool() .should( monthQb.keyword().onField( "mythology" ).matching( "whitening" ).createQuery() ) .should( monthQb.keyword().onField( "history" ).matching( "whitening" ).createQuery() ) .createQuery(); List<Month> results = fullTextSession.createFullTextQuery( query, Month.class ).list(); assertEquals( 2, results.size() ); assertEquals( "January", results.get( 0 ).getName() ); //boosted query, January and february both contain whitening but February in a longer text //since history is boosted, February should come first though query = monthQb .bool() .should( monthQb.keyword().onField( "mythology" ).matching( "whitening" ).createQuery() ) .should( monthQb.keyword().onField( "history" ).boostedTo( 30 ).matching( "whitening" ).createQuery() ) .createQuery(); results = fullTextSession.createFullTextQuery( query, Month.class ).list(); assertEquals( 2, results.size() ); assertEquals( "February", results.get( 0 ).getName() ); //FIXME add other method tests besides boostedTo transaction.commit(); } @SuppressWarnings("unchecked") public void testMultipleFields() throws Exception { Transaction transaction = fullTextSession.beginTransaction(); final QueryBuilder monthQb = fullTextSession.getSearchFactory() .buildQueryBuilder().forEntity( Month.class ).get(); //combined query, January and february both contain whitening but February in a longer text Query query = monthQb.keyword() .onField( "mythology" ) .andField( "history" ) .matching( "whitening" ) .createQuery(); List<Month> results = fullTextSession.createFullTextQuery( query, Month.class ).list(); assertEquals( 2, results.size() ); assertEquals( "January", results.get( 0 ).getName() ); //combined query, January and february both contain whitening but February in a longer text query = monthQb.keyword() .onFields( "mythology", "history" ) .boostedTo( 30 ) .matching( "whitening" ).createQuery(); results = fullTextSession.createFullTextQuery( query, Month.class ).list(); assertEquals( 2, results.size() ); assertEquals( "January", results.get( 0 ).getName() ); //boosted query, January and february both contain whitening but February in a longer text //since history is boosted, February should come first though query = monthQb.keyword() .onField( "mythology" ) .andField( "history" ) .boostedTo( 30 ) .matching( "whitening" ) .createQuery(); results = fullTextSession.createFullTextQuery( query, Month.class ).list(); assertEquals( 2, results.size() ); assertEquals( "February", results.get( 0 ).getName() ); transaction.commit(); } @SuppressWarnings("unchecked") public void testBoolean() throws Exception { Transaction transaction = fullTextSession.beginTransaction(); final QueryBuilder monthQb = fullTextSession.getSearchFactory() .buildQueryBuilder().forEntity( Month.class ).get(); //must Query query = monthQb .bool() .must( monthQb.keyword().onField( "mythology" ).matching( "colder" ).createQuery() ) .createQuery(); List<Month> results = fullTextSession.createFullTextQuery( query, Month.class ).list(); assertEquals( 1, results.size() ); assertEquals( "January", results.get( 0 ).getName() ); //must not + all query = monthQb .bool() .should( monthQb.all().createQuery() ) .must( monthQb.keyword().onField( "mythology" ).matching( "colder" ).createQuery() ) .not() .createQuery(); results = fullTextSession.createFullTextQuery( query, Month.class ).list(); assertEquals( 2, results.size() ); assertEquals( "February", results.get( 0 ).getName() ); assertEquals( "March", results.get( 1 ).getName() ); //implicit must not + all (not recommended) query = monthQb .bool() .must( monthQb.keyword().onField( "mythology" ).matching( "colder" ).createQuery() ) .not() .createQuery(); results = fullTextSession.createFullTextQuery( query, Month.class ).list(); assertEquals( 2, results.size() ); assertEquals( "February", results.get( 0 ).getName() ); assertEquals( "March", results.get( 1 ).getName() ); //all except (recommended) query = monthQb .all() .except( monthQb.keyword().onField( "mythology" ).matching( "colder" ).createQuery() ) .createQuery(); results = fullTextSession.createFullTextQuery( query, Month.class ).list(); assertEquals( 2, results.size() ); assertEquals( "February", results.get( 0 ).getName() ); assertEquals( "March", results.get( 1 ).getName() ); transaction.commit(); } public void testRangeQueryFromTo() throws Exception { Transaction transaction = fullTextSession.beginTransaction(); final QueryBuilder monthQb = fullTextSession.getSearchFactory() .buildQueryBuilder().forEntity( Month.class ).get(); calendar.setTimeZone( TimeZone.getTimeZone( "UTC" ) ); calendar.set( 1900, 2, 12, 0, 0, 0 ); Date from = calendar.getTime(); calendar.set( 1910, 2, 12, 0, 0, 0 ); Date to = calendar.getTime(); Query query = monthQb .range() .onField( "estimatedCreation" ) .andField( "justfortest" ) .ignoreFieldBridge().ignoreAnalyzer() .from( from ) .to( to ).excludeLimit() .createQuery(); assertEquals( 1, fullTextSession.createFullTextQuery( query, Month.class ).getResultSize() ); query = monthQb .range() .onField( "estimatedCreation" ) .ignoreFieldBridge() .andField( "justfortest" ) .ignoreFieldBridge().ignoreAnalyzer() .from( DateTools.dateToString( from, DateTools.Resolution.MINUTE ) ) .to( DateTools.dateToString( to, DateTools.Resolution.MINUTE ) ) .excludeLimit() .createQuery(); assertEquals( 1, fullTextSession.createFullTextQuery( query, Month.class ).getResultSize() ); transaction.commit(); } public void testRangeQueryBelow() throws Exception { Transaction transaction = fullTextSession.beginTransaction(); final QueryBuilder monthQb = fullTextSession.getSearchFactory() .buildQueryBuilder().forEntity( Month.class ).get(); calendar.setTimeZone( TimeZone.getTimeZone( "UTC" ) ); calendar.set( 10 + 1800, 2, 12, 0, 0, 0 ); Date to = calendar.getTime(); Query query = monthQb .range() .onField( "estimatedCreation" ) .andField( "justfortest" ) .ignoreFieldBridge().ignoreAnalyzer() .below( to ) .createQuery(); FullTextQuery hibQuery = fullTextSession.createFullTextQuery( query, Month.class ); assertEquals( 1, hibQuery.getResultSize() ); assertEquals( "March", ( (Month) hibQuery.list().get( 0 ) ).getName() ); query = monthQb .range() .onField( "estimatedCreation" ) .ignoreFieldBridge() .andField( "justfortest" ) .ignoreFieldBridge().ignoreAnalyzer() .below( DateTools.dateToString( to, DateTools.Resolution.MINUTE ) ) .createQuery(); hibQuery = fullTextSession.createFullTextQuery( query, Month.class ); assertEquals( 1, hibQuery.getResultSize() ); assertEquals( "March", ( (Month) hibQuery.list().get( 0 ) ).getName() ); query = monthQb.range() .onField( "raindropInMm" ) .below( 0.24d ) .createQuery(); assertTrue( query.getClass().isAssignableFrom( NumericRangeQuery.class ) ); List results = fullTextSession.createFullTextQuery( query, Month.class ).list(); assertEquals( "test range numeric ", 1, results.size() ); assertEquals( "test range numeric ", "January", ( (Month) results.get( 0 ) ).getName() ); transaction.commit(); } public void testRangeQueryAbove() throws Exception { Transaction transaction = fullTextSession.beginTransaction(); final QueryBuilder monthQb = fullTextSession.getSearchFactory() .buildQueryBuilder().forEntity( Month.class ).get(); calendar.setTimeZone( TimeZone.getTimeZone( "UTC" ) ); calendar.set( 10 + 1900, 2, 12, 0, 0, 0 ); Date to = calendar.getTime(); Query query = monthQb .range() .onField( "estimatedCreation" ) .andField( "justfortest" ) .ignoreFieldBridge().ignoreAnalyzer() .above( to ) .createQuery(); FullTextQuery hibQuery = fullTextSession.createFullTextQuery( query, Month.class ); assertEquals( 1, hibQuery.getResultSize() ); assertEquals( "February", ( (Month) hibQuery.list().get( 0 ) ).getName() ); query = monthQb .range() .onField( "estimatedCreation" ) .ignoreFieldBridge() .andField( "justfortest" ) .ignoreFieldBridge().ignoreAnalyzer() .above( DateTools.dateToString( to, DateTools.Resolution.MINUTE ) ) .createQuery(); hibQuery = fullTextSession.createFullTextQuery( query, Month.class ); assertEquals( 1, hibQuery.getResultSize() ); assertEquals( "February", ( (Month) hibQuery.list().get( 0 ) ).getName() ); // test the limits, inclusive query = monthQb .range() .onField( "estimatedCreation" ) .andField( "justfortest" ) .ignoreFieldBridge().ignoreAnalyzer() .above( february ) .createQuery(); hibQuery = fullTextSession.createFullTextQuery( query, Month.class ); assertEquals( 1, hibQuery.getResultSize() ); assertEquals( "February", ( (Month) hibQuery.list().get( 0 ) ).getName() ); // test the limits, exclusive query = monthQb .range() .onField( "estimatedCreation" ) .andField( "justfortest" ) .ignoreFieldBridge().ignoreAnalyzer() .above( february ).excludeLimit() .createQuery(); hibQuery = fullTextSession.createFullTextQuery( query, Month.class ); assertEquals( 0, hibQuery.getResultSize() ); transaction.commit(); } public void testPhraseQuery() throws Exception { Transaction transaction = fullTextSession.beginTransaction(); final QueryBuilder monthQb = fullTextSession.getSearchFactory() .buildQueryBuilder().forEntity( Month.class ).get(); Query query = monthQb .phrase() .onField( "mythology" ) .sentence( "colder and whitening" ) .createQuery(); assertEquals( "test exact phrase", 1, fullTextSession.createFullTextQuery( query, Month.class ).getResultSize() ); query = monthQb .phrase() .onField( "mythology" ) .sentence( "Month whitening" ) .createQuery(); assertEquals( "test slop", 0, fullTextSession.createFullTextQuery( query, Month.class ).getResultSize() ); query = monthQb .phrase() .withSlop( 3 ) .onField( "mythology" ) .sentence( "Month whitening" ) .createQuery(); assertEquals( "test slop", 1, fullTextSession.createFullTextQuery( query, Month.class ).getResultSize() ); query = monthQb .phrase() .onField( "mythology" ) .sentence( "whitening" ) .createQuery(); assertEquals( "test one term optimization", 1, fullTextSession.createFullTextQuery( query, Month.class ).getResultSize() ); //Does not work as the NGram filter does not seem to be skipping posiional increment between ngrams. // query = monthQb // .phrase() // .onField( "mythology_ngram" ) // .sentence( "snobored" ) // .createQuery(); // // assertEquals( 1, fullTextSession.createFullTextQuery( query, Month.class ).getResultSize() ); transaction.commit(); } @TestForIssue(jiraKey = "HSEARCH-1074") public void testPhraseQueryWithNoTermsAfterAnalyzerApplication() throws Exception { Transaction transaction = fullTextSession.beginTransaction(); final QueryBuilder monthQb = fullTextSession.getSearchFactory() .buildQueryBuilder().forEntity( Month.class ).get(); Query query = monthQb. phrase() .onField( "mythology" ) .sentence( "and" ) .createQuery(); assertEquals( "there should be no results, since all terms are stop words", 0, fullTextSession.createFullTextQuery( query, Month.class ).getResultSize() ); transaction.commit(); } public void testNumericRangeQueries() { Transaction transaction = fullTextSession.beginTransaction(); final QueryBuilder monthQb = fullTextSession.getSearchFactory() .buildQueryBuilder().forEntity( Month.class ).get(); Query query = monthQb .range() .onField( "raindropInMm" ) .from( 0.23d ) .to( 0.24d ) .createQuery(); assertTrue( query.getClass().isAssignableFrom( NumericRangeQuery.class ) ); List results = fullTextSession.createFullTextQuery( query, Month.class ).list(); assertEquals( "test range numeric ", 1, results.size() ); assertEquals( "test range numeric ", "January", ( (Month) results.get( 0 ) ).getName() ); transaction.commit(); } public void testNumericFieldsTermQuery() { Transaction transaction = fullTextSession.beginTransaction(); final QueryBuilder monthQb = fullTextSession.getSearchFactory() .buildQueryBuilder().forEntity( Month.class ).get(); Query query = monthQb.keyword() .onField( "raindropInMm" ) .matching( 0.231d ) .createQuery(); assertTrue( query.getClass().isAssignableFrom( NumericRangeQuery.class ) ); assertEquals( "test term numeric ", 1, fullTextSession.createFullTextQuery( query, Month.class ).getResultSize() ); transaction.commit(); } public void testFieldBridge() { Transaction transaction = fullTextSession.beginTransaction(); final QueryBuilder monthQb = fullTextSession.getSearchFactory() .buildQueryBuilder().forEntity( Month.class ).get(); Query query = monthQb.keyword() .onField( "monthRomanNumber" ) .matching( 2 ) .createQuery(); FullTextQuery fullTextQuery = fullTextSession.createFullTextQuery( query, Month.class ); List results = fullTextQuery.list(); assertEquals( 1, results.size() ); Month february = (Month) results.get( 0 ); assertEquals( 2, february.getMonthValue() ); transaction.commit(); } public void testSpatialQueries() { Transaction transaction = fullTextSession.beginTransaction(); final QueryBuilder builder = fullTextSession.getSearchFactory() .buildQueryBuilder().forEntity( POI.class ).get(); Coordinates coordinates = Point.fromDegrees( 24d, 31.5d ); Query query = builder .spatial() .onCoordinates( "location" ) .within( 51, Unit.KM ) .ofCoordinates( coordinates ) .createQuery(); List results = fullTextSession.createFullTextQuery( query, POI.class ).list(); assertEquals( "test quad tree based spatial query", 1, results.size() ); assertEquals( "test quad tree based spatial query", "Bozo", ( (POI) results.get( 0 ) ).getName() ); query = builder .spatial() .onCoordinates( "location" ) .within( 500, Unit.KM ) .ofLatitude( 48.858333d ).andLongitude( 2.294444d ) .createQuery(); results = fullTextSession.createFullTextQuery( query, POI.class ).list(); assertEquals( "test quad tree based spatial query", 1, results.size() ); assertEquals( "test quad tree based spatial query", "Tour Eiffel", ( (POI) results.get( 0 ) ).getName() ); transaction.commit(); } private void indexTestData() { Transaction tx = fullTextSession.beginTransaction(); final Calendar calendar = Calendar.getInstance(); calendar.setTimeZone( TimeZone.getTimeZone( "UTC" ) ); calendar.set( 1900, 2, 12, 0, 0, 0 ); january = calendar.getTime(); fullTextSession.persist( new Month( "January", 1, "Month of colder and whitening", "Historically colder than any other month in the northern hemisphere", january, 0.231d ) ); calendar.set( 100 + 1900, 2, 12, 0, 0, 0 ); february = calendar.getTime(); fullTextSession.persist( new Month( "February", 2, "Month of snowboarding", "Historically, the month where we make babies while watching the whitening landscape", february, 0.435d ) ); calendar.set( 1800, 2, 12, 0, 0, 0 ); march = calendar.getTime(); fullTextSession.persist( new Month( "March", 3, "Month of fake spring", "Historically, the month in which we actually find time to go snowboarding.", march, 0.435d ) ); POI poi = new POI( 1, "Tour Eiffel", 48.858333d, 2.294444d, "Monument" ); fullTextSession.persist( poi ); poi = new POI( 2, "Bozo", 24d, 32d, "Monument" ); fullTextSession.persist( poi ); tx.commit(); fullTextSession.clear(); } private void cleanUpTestData() { if ( !fullTextSession.isOpen() ) { return; } Transaction tx = fullTextSession.getTransaction(); if ( tx.isActive() ) { //to not hide reason for test failures, as it otherwise causes a nested transaction not supported exception tx.commit(); } tx = fullTextSession.beginTransaction(); @SuppressWarnings("unchecked") final List<Object> results = fullTextSession.createQuery( "from " + Object.class.getName() ).list(); assertEquals( 5, results.size() ); for ( Object entity : results ) { fullTextSession.delete( entity ); } tx.commit(); fullTextSession.close(); } @Override protected Class<?>[] getAnnotatedClasses() { return new Class<?>[] { Month.class, POI.class }; } @Override protected void configure(Configuration cfg) { super.configure( cfg ); cfg.getProperties().put( Environment.MODEL_MAPPING, MappingFactory.class.getName() ); } public static class MappingFactory { @Factory public SearchMapping build() { SearchMapping mapping = new SearchMapping(); mapping .analyzerDef( "stemmer", StandardTokenizerFactory.class ) .filter( StandardFilterFactory.class ) .filter( LowerCaseFilterFactory.class ) .filter( StopFilterFactory.class ) .filter( SnowballPorterFilterFactory.class ) .param( "language", "English" ) .analyzerDef( "ngram", StandardTokenizerFactory.class ) .filter( StandardFilterFactory.class ) .filter( LowerCaseFilterFactory.class ) .filter( StopFilterFactory.class ) .filter( NGramFilterFactory.class ) .param( "minGramSize", "3" ) .param( "maxGramSize", "3" ); return mapping; } } }