package querqy.solr; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.Query; import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.common.params.DisMaxParams; import org.apache.solr.common.params.HighlightParams; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.search.QueryParsing; import org.apache.solr.search.WrappedQuery; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import querqy.parser.WhiteSpaceQuerqyParser; import querqy.rewrite.RewriteChain; @SolrTestCaseJ4.SuppressSSL public class DefaultQuerqyDismaxQParserTest extends SolrTestCaseJ4 { public void index() throws Exception { assertU(adoc("id", "1", "f1", "a")); assertU(adoc("id", "2", "f1", "a")); assertU(adoc("id", "3", "f2", "a")); assertU(adoc("id", "4", "f1", "b")); assertU(adoc("id", "5", "f1", "spellcheck", "f2", "test")); assertU(adoc("id", "6", "f1", "spellcheck filtered", "f2", "test")); assertU(adoc("id", "7", "f1", "aaa")); assertU(adoc("id", "8", "f1", "aaa bbb ccc", "f2", "w87")); assertU(adoc("id", "9", "f1", "ignore o u s")); assertU(commit()); } @BeforeClass public static void beforeTests() throws Exception { initCore("solrconfig-DefaultQuerqyDismaxQParserTest.xml", "schema.xml"); } @Override @Before public void setUp() throws Exception { super.setUp(); clearIndex(); index(); } @Test public void testLocalParams() throws Exception { SolrQueryRequest req = req("q", "{!querqy qf='f1 f2'}a b"); assertQ("local params don't work", req,"//result[@name='response' and @numFound='4']"); req.close(); } @Test public void testThatFilterRulesFromCollationDontEndUpInMainQuery() throws Exception { SolrQueryRequest req0 = req("q", "spellcheck test", DisMaxParams.QF, "f1 f2", DisMaxParams.MM, "2", "defType", "querqy", "debugQuery", "true" ); assertQ("Filter expected", req0, "//result[@name='response' and @numFound='1']", "//arr[@name='parsed_filter_queries']/str[text() = 'f1:filtered']" ); req0.close(); SolrQueryRequest req = req("q", "spellhceck test", DisMaxParams.QF, "f1 f2", DisMaxParams.MM, "2", "spellcheck.collate", "true", "spellcheck.maxCollations", "1", "spellcheck.maxCollationTries", "10", "spellcheck", "true", "defType", "querqy", "debugQuery", "true" ); assertQ("Combination with collations doesn't work", req, "//result[@name='response' and @numFound='0']", "//str[@name='collation'][text() = 'spellcheck test']", "not(//arr[@name='parsed_filter_queries']/str[text() = 'f1:filtered'])" ); req.close(); } @Test public void testThatAMMof2getsSetFor3optionalClauses() throws Exception { SolrQueryRequest req = req("q", "a b c", DisMaxParams.QF, "f1 f2", DisMaxParams.MM, "2" ); QuerqyDismaxQParser parser = new QuerqyDismaxQParser("a b c", null, req.getParams(), req, new RewriteChain(), new WhiteSpaceQuerqyParser(), null); Query query = parser.parse(); req.close(); BooleanQuery bq = assertBQ(query); assertEquals(2, bq.getMinimumNumberShouldMatch()); } @Test public void testThatMMIsAppliedWhileQueryContainsMUSTBooleanOperators() throws Exception { String q = "a +b c +d e f"; SolrQueryRequest req = req("q", q, DisMaxParams.QF, "f1 f2", DisMaxParams.MM, "3"); QuerqyDismaxQParser parser = new QuerqyDismaxQParser(q, null, req.getParams(), req, new RewriteChain(), new WhiteSpaceQuerqyParser(), null); Query query = parser.parse(); req.close(); BooleanQuery bq = assertBQ(query); assertEquals(3, bq.getMinimumNumberShouldMatch()); } @Test public void testThatMMIsNotAppliedWhileQueryContainsMUSTNOTBooleanOperator() throws Exception { String q = "a +b c -d e f"; SolrQueryRequest req = req("q", q, DisMaxParams.QF, "f1 f2", DisMaxParams.MM, "3", QueryParsing.OP, "OR" ); QuerqyDismaxQParser parser = new QuerqyDismaxQParser(q, null, req.getParams(), req, new RewriteChain(), new WhiteSpaceQuerqyParser(), null); Query query = parser.parse(); req.close(); BooleanQuery bq = assertBQ(query); assertEquals(0, bq.getMinimumNumberShouldMatch()); } @Test public void testThatPfIsAppliedOnlyToExistingField() throws Exception { String q = "a b c d"; SolrQueryRequest req = req("q", q, DisMaxParams.QF, "f1 f2", DisMaxParams.MM, "3", QueryParsing.OP, "OR", DisMaxParams.PF, "f3^2 f40^0.5", "defType", "querqy", "debugQuery", "true" ); // f4 doesn't exist assertQ("wrong ps", req, "//str[@name='parsedquery'][contains(.,'PhraseQuery(f3:\"a b c d\")^2.0')]", "//str[@name='parsedquery'][not(contains(.,'PhraseQuery(f40:\"a b c d\")^0.5'))]"); req.close(); } @Test public void testThatMatchAllDoesNotThrowException() throws Exception { String q = "*:*"; SolrQueryRequest req = req("q", q, DisMaxParams.QF, "f1 f2", DisMaxParams.MM, "3", QueryParsing.OP, "OR", DisMaxParams.PF, "f3^2 f4^0.5", "defType", "querqy", "debugQuery", "true" ); assertQ("Matchall fails", req, "//str[@name='parsedquery'][contains(.,'*:*')]", "//result[@name='response' and @numFound='9']" ); req.close(); } @Test public void testThatPfIsAppliedOnlyToFieldsWithTermPositions() throws Exception { String q = "a b c d"; SolrQueryRequest req = req("q", q, DisMaxParams.QF, "f1 f2", DisMaxParams.MM, "3", QueryParsing.OP, "OR", "defType", "querqy", "debugQuery", "true", DisMaxParams.PF, "f1^1.2 str^2 f_no_tfp^0.5"); // str is a string field // f_no_tfp / f_no_tp don't have term positions assertQ("wrong ps2", req, "//str[@name='parsedquery'][contains(.,'PhraseQuery(f1:\"a b c d\")^1.2')]", "//str[@name='parsedquery'][not(contains(.,'PhraseQuery(str:\"a b c d\")^2.0'))]", "//str[@name='parsedquery'][not(contains(.,'PhraseQuery(f_no_tfp:\"a b c d\")^0.5'))]", "//str[@name='parsedquery'][not(contains(.,'PhraseQuery(f_no_tp:\"a b c d\")^0.5'))]"); req.close(); } @Test public void testThatAnalysisIsRunForPf() throws Exception { String q = "K L M"; SolrQueryRequest req = req("q", q, DisMaxParams.QF, "f1", DisMaxParams.MM, "3", QueryParsing.OP, "OR", "defType", "querqy", "debugQuery", "true", DisMaxParams.PF, "f1_lc f2_lc"); assertQ("Analysis not applied for pf", req, // Query terms should be lower-cased in analysis for pf fields "//str[@name='parsedquery'][contains(.,'DisjunctionMaxQuery((f1_lc:\"k l m\" | f2_lc:\"k l m\"))')]", // but not for query fields "//str[@name='parsedquery'][contains(.,'(f1:K f1:L f1:M)')]"); req.close(); } @Test public void testThatAnalysisIsRunForPf2() throws Exception { String q = "K L M"; SolrQueryRequest req = req("q", q, DisMaxParams.QF, "f1", DisMaxParams.MM, "3", QueryParsing.OP, "OR", "defType", "querqy", "debugQuery", "true", DisMaxParams.PF2, "f1_lc f2_lc"); assertQ("Analysis not applied for pf2", req, // Query terms should be lower-cased in analysis for pf2 fields "//str[@name='parsedquery'][contains(.,'DisjunctionMaxQuery(((f1_lc:\"k l\" f1_lc:\"l m\") | (f2_lc:\"k l\" f2_lc:\"l m\")))')]", // but not for query fields "//str[@name='parsedquery'][contains(.,'(f1:K f1:L f1:M)')]"); req.close(); } @Test public void testThatAnalysisIsRunForPf3() throws Exception { String q = "K L M"; SolrQueryRequest req = req("q", q, DisMaxParams.QF, "f1", DisMaxParams.MM, "3", QueryParsing.OP, "OR", "defType", "querqy", "debugQuery", "true", DisMaxParams.PF3, "f1_lc f2_lc"); assertQ("Analysis not applied for pf", req, // Query terms should be lower-cased in analysis for pf fields "//str[@name='parsedquery'][contains(.,'DisjunctionMaxQuery((f1_lc:\"k l m\" | f2_lc:\"k l m\"))')]", // but not for query fields "//str[@name='parsedquery'][contains(.,'(f1:K f1:L f1:M)')]"); req.close(); } @Test public void testThatPf2IsAppliedOnlyToExistingField() throws Exception { String q = "a b c d"; SolrQueryRequest req = req("q", q, DisMaxParams.QF, "f1 f2", DisMaxParams.MM, "3", QueryParsing.OP, "OR", "defType", "querqy", "debugQuery", "true", DisMaxParams.PF2, "f1^2 f40^0.5"); // f40 does not exists assertQ("wrong ps2", req, "//str[@name='parsedquery_toString'][contains(.,'(f1:\"a b\" f1:\"b c\" f1:\"c d\")^2.0')]", "//str[@name='parsedquery_toString'][not(contains(.,'(f40:\"a b\" f1:\"b c\" f1:\"c d\")^0.5'))]"); req.close(); } @Test public void testThatPf2IsAppliedOnlyToFieldsWithTermPositions() throws Exception { String q = "a b c d"; SolrQueryRequest req = req("q", q, DisMaxParams.QF, "f1 f2", DisMaxParams.MM, "3", QueryParsing.OP, "OR", "defType", "querqy", "debugQuery", "true", DisMaxParams.PF2, "f1 str f_no_tfp"); // str is a string field // f_no_tfp doesn't have term positions assertQ("wrong ps2", req, "//str[@name='parsedquery'][contains(.,'f1:\"a b\"')]", "//str[@name='parsedquery'][contains(.,'f1:\"b c\"')]", "//str[@name='parsedquery'][contains(.,'f1:\"c d\"')]", "//str[@name='parsedquery'][not(contains(.,'str:\"a b\"'))]", "//str[@name='parsedquery'][not(contains(.,'str:\"b c\"'))]", "//str[@name='parsedquery'][not(contains(.,'str:\"c d\"'))]", "//str[@name='parsedquery'][not(contains(.,'f_no_tfp:\"a b\"'))]", "//str[@name='parsedquery'][not(contains(.,'f_no_tfp:\"b c\"'))]", "//str[@name='parsedquery'][not(contains(.,'f_no_tfp:\"c d\"'))]"); req.close(); } @Test public void testThatPf3IsAppliedOnlyToFieldsWithTermPositions() throws Exception { String q = "a b c d"; SolrQueryRequest req = req("q", q, DisMaxParams.QF, "f1 f2", DisMaxParams.MM, "3", QueryParsing.OP, "OR", "defType", "querqy", "debugQuery", "true", DisMaxParams.PF3, "f1^1.2 str^2 f_no_tfp^0.5 f4^4"); // str is a string field // f_no_tfp / f_no_tp don't have term positions assertQ("wrong ps2", req, "//str[@name='parsedquery'][contains(.,'(f1:\"a b c\" f1:\"b c d\")^1.2')]", "//str[@name='parsedquery'][not(contains(.,'str:\"a b c\"^2.0'))]", "//str[@name='parsedquery'][not(contains(.,'str:\"b c d\"^2.0'))]", "//str[@name='parsedquery'][not(contains(.,'f_no_tfp:\"a b c\"^0.5'))]", "//str[@name='parsedquery'][not(contains(.,'f_no_tfp:\"b c d\"^0.5'))]", "//str[@name='parsedquery'][not(contains(.,'f_no_tp:\"a b c\"^0.5'))]", "//str[@name='parsedquery'][not(contains(.,'f_no_tp:\"b c d\"^0.5'))]"); req.close(); } @Test public void testThatPf3IsApplied() throws Exception { String q = "a b c d"; SolrQueryRequest req = req("q", q, DisMaxParams.QF, "f1 f2", DisMaxParams.MM, "3", QueryParsing.OP, "OR", DisMaxParams.PF3, "f2^2.5 f3^1.5" ); verifyQueryString(req, q, "(f2:\"a b c\" f2:\"b c d\")^2.5", "(f3:\"a b c\" f3:\"b c d\")^1.5" ); } @Test public void testThatPFSkipsMustNotClauses() throws Exception { String q = "a b -c d e f"; SolrQueryRequest req = req("q", q, DisMaxParams.QF, "f1 f2", DisMaxParams.MM, "3", QueryParsing.OP, "OR", DisMaxParams.PF, "f2^1.5 f3^1.5", DisMaxParams.PF2, "f1^2.1 f2^2.1", DisMaxParams.PF3, "f3^3.9 f1^3.9" ); verifyQueryString(req, q, "(f2:\"a b d e f\")^1.5", "(f3:\"a b d e f\")^1.5", "(f1:\"a b\" f1:\"b d\" f1:\"d e\" f1:\"e f\")^2.1", "(f2:\"a b\" f2:\"b d\" f2:\"d e\" f2:\"e f\")^2.1", "(f3:\"a b d\" f3:\"b d e\" f3:\"d e f\")^3.9", "(f1:\"a b d\" f1:\"b d e\" f1:\"d e f\")^3.9" ); } @Test public void testThatPFWorksWithSynonymRewriting() throws Exception { SolrQueryRequest req = req("q", "a b", DisMaxParams.QF, "f1 f2^0.9", DisMaxParams.PF, "f1^0.5", "defType", "querqy", "debugQuery", "true"); assertQ("ps with synonyms not working", req, "//str[@name='parsedquery'][contains(.,'PhraseQuery(f1:\"a b\")^0.5')]"); req.close(); } @Test public void testThatPF23FWorksWithSynonymRewriting() throws Exception { SolrQueryRequest req = req("q", "a b c d", DisMaxParams.QF, "f1 f2^0.9", DisMaxParams.PF2, "f1~2^2.1", DisMaxParams.PF3, "f2~3^3.9", "defType", "querqy", "debugQuery", "true"); assertQ("ps2/3 with synonyms not working", req, "//str[@name='parsedquery'][contains(.,'(f1:\"a b\"~2 f1:\"b c\"~2 f1:\"c d\"~2)^2.1')]", "//str[@name='parsedquery'][contains(.,'(f2:\"a b c\"~3 f2:\"b c d\"~3)^3.9')]"); req.close(); } @Test public void testThatGeneratedTermsArePenalised() throws Exception { SolrQueryRequest req = req("q", "a b", DisMaxParams.QF, "f1^2", DisMaxParams.PF, "f1^0.5", QuerqyDismaxQParser.GFB, "0.8", "defType", "querqy", "debugQuery", "true"); assertQ(QuerqyDismaxQParser.GFB + " not working", req, "//str[@name='parsedquery'][contains(.,'f1:a^2.0 | f1:x^1.6')]", "//str[@name='parsedquery'][contains(.,'PhraseQuery(f1:\"a b\")^0.5')]"); req.close(); } @Test public void testThatGeneratedQueryFieldBoostsAreApplied() throws Exception { SolrQueryRequest req = req("q", "a", DisMaxParams.QF, "f1^2 f2^3", QuerqyDismaxQParser.GFB, "0.8", QuerqyDismaxQParser.GQF, "f2^10", "defType", "querqy", "debugQuery", "true"); assertQ("Generated query field boosts not working", req, "//str[@name='parsedquery'][contains(.,'f1:a^2.0 | f2:a^3.0 | f2:x^10.0')]" ); req.close(); } @Test public void testThatGeneratedQueryFieldsAreApplied() throws Exception { SolrQueryRequest req = req("q", "a", DisMaxParams.QF, "f1^2 f2^3", QuerqyDismaxQParser.GFB, "0.8", QuerqyDismaxQParser.GQF, "f2 f4", "defType", "querqy", "debugQuery", "true"); assertQ("Generated query fields not working", req, "//str[@name='parsedquery'][contains(.,'f1:a^2.0 | f2:a^3.0 | f2:x^2.4 | f4:x^0.8')]" ); req.close(); } @Test public void testThatUpRuleCanPickUpPlaceHolder() throws Exception { SolrQueryRequest req = req("q", "aaa", DisMaxParams.QF, "f1 f2", "defType", "querqy", "debugQuery", "true"); assertQ("Default ranking for picking up wildcard not working", req, "//result/doc[1]/str[@name='id'][text()='7']" ); req.close(); SolrQueryRequest req2 = req("q", "aaa w87", DisMaxParams.QF, "f1 f2", DisMaxParams.MM, "100%", "defType", "querqy", "debugQuery", "true"); assertQ("Ranking for picking up wildcard not working", req2, "//result/doc[1]/str[@name='id'][text()='8']", "//result[@name='response' and @numFound='2']" ); req2.close(); } @Test public void testThatHighlightingIsApplied() throws Exception { SolrQueryRequest req = req("q", "a", DisMaxParams.QF, "f1", HighlightParams.HIGHLIGHT, "true", HighlightParams.FIELDS, "f1", HighlightParams.SIMPLE_PRE, "PRE", HighlightParams.SIMPLE_POST, "POST", "defType", "querqy", "debugQuery", "true"); assertQ("Highlighting not working", req, "//lst[@name='highlighting']//arr[@name='f1']/str[text()='PREaPOST']" ); req.close(); } @Test public void testThatHighlightingIsNotAppliedToBoostQuery() throws Exception { SolrQueryRequest req = req("q", "o", DisMaxParams.QF, "f1", HighlightParams.HIGHLIGHT, "true", HighlightParams.FIELDS, "f1", HighlightParams.SIMPLE_PRE, "PRE", HighlightParams.SIMPLE_POST, "POST", "defType", "querqy", "debugQuery", "true"); assertQ("UP token is highlighted", req, "//lst[@name='highlighting']//arr[@name='f1']/str[not(contains(.,'PREuPOST'))]" ); req.close(); } @Test public void testThatHighlightingIsAppliedToSynonyms() throws Exception { SolrQueryRequest req = req("q", "o", DisMaxParams.QF, "f1", HighlightParams.HIGHLIGHT, "true", HighlightParams.FIELDS, "f1", HighlightParams.SIMPLE_PRE, "PRE", HighlightParams.SIMPLE_POST, "POST", "defType", "querqy", "debugQuery", "true"); assertQ("UP token is highlighted", req, "//lst[@name='highlighting']//arr[@name='f1']/str[contains(.,'PREsPOST')]" ); req.close(); } public void verifyQueryString(SolrQueryRequest req, String q, String... expectedSubstrings) throws Exception { QuerqyDismaxQParser parser = new QuerqyDismaxQParser(q, null, req.getParams(), req, new RewriteChain(), new WhiteSpaceQuerqyParser(), null); Query query = parser.parse(); req.close(); assertTrue(query instanceof BooleanQuery); BooleanQuery bq = (BooleanQuery) query; String qStr = bq.toString(); for (String exp : expectedSubstrings) { assertTrue("Missing: " + exp + " in " + bq, qStr.indexOf(exp) > -1); } } protected BooleanQuery assertBQ(Query query) { BooleanQuery bq = null; if (query instanceof WrappedQuery) { Query w = ((WrappedQuery) query).getWrappedQuery(); assertTrue(w instanceof BooleanQuery); bq = (BooleanQuery) w; } else { assertTrue(query instanceof BooleanQuery); bq = (BooleanQuery) query; } return bq; } }