package org.apache.solr.search; /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.util.HashSet; import java.util.Set; import org.apache.lucene.search.Query; import org.apache.lucene.search.QueryUtils; import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrRequestInfo; import org.apache.solr.response.SolrQueryResponse; import org.junit.AfterClass; import org.junit.BeforeClass; /** * Sanity checks that queries (generated by the QParser and ValueSourceParser * framework) are appropraitely {@link Object#equals} and * {@link Object#hashCode()} equivilent. If you are adding a new default * QParser or ValueSourceParser, you will most likely get a failure from * {@link #testParserCoverage} until you add a new test method to this class. * * @see ValueSourceParser#standardValueSourceParsers * @see QParserPlugin#standardPlugins * @see QueryUtils **/ public class QueryEqualityTest extends SolrTestCaseJ4 { @BeforeClass public static void beforeClass() throws Exception { initCore("solrconfig.xml","schema15.xml"); } /** @see #testParserCoverage */ @AfterClass public static void afterClassParserCoverageTest() { if ( ! doAssertParserCoverage) return; for (int i=0; i < QParserPlugin.standardPlugins.length; i+=2) { assertTrue("qparser #"+i+" name not a string", QParserPlugin.standardPlugins[i] instanceof String); final String name = (String)QParserPlugin.standardPlugins[i]; assertTrue("testParserCoverage was run w/o any other method explicitly testing qparser: " + name, qParsersTested.contains(name)); } for (final String name : ValueSourceParser.standardValueSourceParsers.keySet()) { assertTrue("testParserCoverage was run w/o any other method explicitly testing val parser: " + name, valParsersTested.contains(name)); } } /** @see #testParserCoverage */ private static boolean doAssertParserCoverage = false; /** @see #testParserCoverage */ private static final Set<String> qParsersTested = new HashSet<String>(); /** @see #testParserCoverage */ private static final Set<String> valParsersTested = new HashSet<String>(); public void testQueryLucene() throws Exception { assertQueryEquals("lucene", "{!lucene}apache solr", "apache solr", "apache solr "); assertQueryEquals("lucene", "+apache +solr", "apache AND solr", " +apache +solr"); } public void testQueryLucenePlusSort() throws Exception { assertQueryEquals("lucenePlusSort", "apache solr", "apache solr", "apache solr ; score desc"); assertQueryEquals("lucenePlusSort", "+apache +solr", "apache AND solr", " +apache +solr; score desc"); } public void testQueryPrefix() throws Exception { SolrQueryRequest req = req("myField","foo_s"); try { assertQueryEquals("prefix", req, "{!prefix f=$myField}asdf", "{!prefix f=foo_s}asdf"); } finally { req.close(); } } public void testQueryBoost() throws Exception { SolrQueryRequest req = req("df","foo_s","myBoost","sum(3,foo_i)"); try { assertQueryEquals("boost", req, "{!boost b=$myBoost}asdf", "{!boost b=$myBoost v=asdf}", "{!boost b=sum(3,foo_i)}foo_s:asdf"); } finally { req.close(); } } public void testQueryDismax() throws Exception { for (final String type : new String[]{"dismax","edismax"}) { assertQueryEquals(type, "{!"+type+"}apache solr", "apache solr", "apache solr", "apache solr "); assertQueryEquals(type, "+apache +solr", "apache AND solr", " +apache +solr"); } } public void testField() throws Exception { SolrQueryRequest req = req("myField","foo_s"); try { assertQueryEquals("field", req, "{!field f=$myField}asdf", "{!field f=$myField v=asdf}", "{!field f=foo_s}asdf"); } finally { req.close(); } } public void testQueryRaw() throws Exception { SolrQueryRequest req = req("myField","foo_s"); try { assertQueryEquals("raw", req, "{!raw f=$myField}asdf", "{!raw f=$myField v=asdf}", "{!raw f=foo_s}asdf"); } finally { req.close(); } } public void testQueryTerm() throws Exception { SolrQueryRequest req = req("myField","foo_s"); try { assertQueryEquals("term", req, "{!term f=$myField}asdf", "{!term f=$myField v=asdf}", "{!term f=foo_s}asdf"); } finally { req.close(); } } public void testQueryNested() throws Exception { SolrQueryRequest req = req("df", "foo_s"); try { assertQueryEquals("query", req, "{!query defType=lucene}asdf", "{!query v='foo_s:asdf'}", "{!query}foo_s:asdf", "{!query}asdf"); } finally { req.close(); } } public void testQueryFunc() throws Exception { // more involved tests of specific functions in other methods SolrQueryRequest req = req("myVar", "5", "myField","foo_i", "myInner","product(4,foo_i)"); try { assertQueryEquals("func", req, "{!func}sum(4,5)", "{!func}sum(4,$myVar)", "sum(4,5)"); assertQueryEquals("func", req, "{!func}sum(1,2,3,4,5)", "{!func}sum(1,2,3,4,$myVar)", "sum(1,2,3,4,5)"); assertQueryEquals("func", req, "{!func}sum(4,$myInner)", "{!func}sum(4,product(4,foo_i))", "{!func}sum(4,product(4,$myField))", "{!func}sum(4,product(4,field(foo_i)))"); } finally { req.close(); } } public void testQueryFrange() throws Exception { SolrQueryRequest req = req("myVar", "5", "low","0.2", "high", "20.4", "myField","foo_i", "myInner","product(4,foo_i)"); try { assertQueryEquals("frange", req, "{!frange l=0.2 h=20.4}sum(4,5)", "{!frange l=$low h=$high}sum(4,$myVar)"); } finally { req.close(); } } public void testQueryGeofilt() throws Exception { checkQuerySpatial("geofilt"); } public void testQueryBbox() throws Exception { checkQuerySpatial("bbox"); } private void checkQuerySpatial(final String type) throws Exception { SolrQueryRequest req = req("myVar", "5", "d","109", "pt","10.312,-20.556", "sfield","store"); try { assertQueryEquals(type, req, "{!"+type+" d=109}", "{!"+type+" sfield=$sfield}", "{!"+type+" sfield=store d=109}", "{!"+type+" sfield=store d=$d pt=$pt}", "{!"+type+" sfield=store d=$d pt=10.312,-20.556}", "{!"+type+"}"); // diff SpatialQueryable FieldTypes matter for determining final query assertQueryEquals(type, req, "{!"+type+" sfield=point_hash}", "{!"+type+" sfield=point_hash d=109}", "{!"+type+" sfield=point_hash d=$d pt=$pt}", "{!"+type+" sfield=point_hash d=$d pt=10.312,-20.556}"); assertQueryEquals(type, req, "{!"+type+" sfield=point}", "{!"+type+" sfield=point d=109}", "{!"+type+" sfield=point d=$d pt=$pt}", "{!"+type+" sfield=point d=$d pt=10.312,-20.556}"); } finally { req.close(); } } public void testQueryJoin() throws Exception { SolrQueryRequest req = req("myVar", "5", "df","text", "ff","foo_s", "tt", "bar_s"); try { assertQueryEquals("join", req, "{!join from=foo_s to=bar_s}asdf", "{!join from=$ff to=$tt}asdf", "{!join from=$ff to='bar_s'}text:asdf"); } finally { req.close(); } } public void testQuerySurround() throws Exception { assertQueryEquals("surround", "{!surround}and(apache,solr)", "and(apache,solr)", "apache AND solr"); } public void testFuncTestfunc() throws Exception { assertFuncEquals("testfunc(foo_i)","testfunc(field(foo_i))"); assertFuncEquals("testfunc(23)"); assertFuncEquals("testfunc(sum(23,foo_i))", "testfunc(sum(23,field(foo_i)))"); } public void testFuncOrd() throws Exception { assertFuncEquals("ord(foo_s)","ord(foo_s )"); } public void testFuncLiteral() throws Exception { SolrQueryRequest req = req("someVar","a string"); try { assertFuncEquals(req, "literal('a string')","literal(\"a string\")", "literal($someVar)"); } finally { req.close(); } } public void testFuncRord() throws Exception { assertFuncEquals("rord(foo_s)","rord(foo_s )"); } public void testFuncTop() throws Exception { assertFuncEquals("top(sum(3,foo_i))"); } public void testFuncLinear() throws Exception { SolrQueryRequest req = req("someVar","27"); try { assertFuncEquals(req, "linear(foo_i,$someVar,42)", "linear(foo_i, 27, 42)"); } finally { req.close(); } } public void testFuncRecip() throws Exception { SolrQueryRequest req = req("someVar","27"); try { assertFuncEquals(req, "recip(foo_i,$someVar,42, 27 )", "recip(foo_i, 27, 42,$someVar)"); } finally { req.close(); } } public void testFuncScale() throws Exception { SolrQueryRequest req = req("someVar","27"); try { assertFuncEquals(req, "scale(field(foo_i),$someVar,42)", "scale(foo_i, 27, 42)"); } finally { req.close(); } } public void testFuncDiv() throws Exception { assertFuncEquals("div(5,4)", "div(5, 4)"); assertFuncEquals("div(foo_i,4)", "div(foo_i, 4)", "div(field('foo_i'), 4)"); assertFuncEquals("div(foo_i,sub(4,field('bar_i')))", "div(field(foo_i), sub(4,bar_i))"); } public void testFuncMod() throws Exception { assertFuncEquals("mod(5,4)", "mod(5, 4)"); assertFuncEquals("mod(foo_i,4)", "mod(foo_i, 4)", "mod(field('foo_i'), 4)"); assertFuncEquals("mod(foo_i,sub(4,field('bar_i')))", "mod(field(foo_i), sub(4,bar_i))"); } public void testFuncMap() throws Exception { assertFuncEquals("map(field(foo_i), 0, 45, 100)", "map(foo_i, 0.0, 45, 100)"); } public void testFuncSum() throws Exception { assertFuncEquals("sum(5,4)", "add(5, 4)"); assertFuncEquals("sum(5,4,3,2,1)", "add(5, 4, 3, 2, 1)"); assertFuncEquals("sum(foo_i,4)", "sum(foo_i, 4)", "sum(field('foo_i'), 4)"); assertFuncEquals("add(foo_i,sub(4,field('bar_i')))", "sum(field(foo_i), sub(4,bar_i))"); } public void testFuncProduct() throws Exception { assertFuncEquals("product(5,4,3,2,1)", "mul(5, 4, 3, 2, 1)"); assertFuncEquals("product(5,4)", "mul(5, 4)"); assertFuncEquals("product(foo_i,4)", "product(foo_i, 4)", "product(field('foo_i'), 4)"); assertFuncEquals("mul(foo_i,sub(4,field('bar_i')))", "product(field(foo_i), sub(4,bar_i))"); } public void testFuncSub() throws Exception { assertFuncEquals("sub(5,4)", "sub(5, 4)"); assertFuncEquals("sub(foo_i,4)", "sub(foo_i, 4)"); assertFuncEquals("sub(foo_i,sum(4,bar_i))", "sub(foo_i, sum(4,bar_i))"); } public void testFuncVector() throws Exception { assertFuncEquals("vector(5,4, field(foo_i))", "vector(5, 4, foo_i)"); assertFuncEquals("vector(foo_i,4)", "vector(foo_i, 4)"); assertFuncEquals("vector(foo_i,sum(4,bar_i))", "vector(foo_i, sum(4,bar_i))"); } public void testFuncQuery() throws Exception { SolrQueryRequest req = req("myQ","asdf"); try { assertFuncEquals(req, "query($myQ)", "query($myQ,0)", "query({!lucene v=$myQ},0)"); } finally { req.close(); } } public void testFuncBoost() throws Exception { SolrQueryRequest req = req("myQ","asdf"); try { assertFuncEquals(req, "boost($myQ,sum(4,5))", "boost({!lucene v=$myQ},sum(4,5))"); } finally { req.close(); } } public void testFuncJoindf() throws Exception { assertFuncEquals("joindf(foo,bar)"); } public void testFuncGeodist() throws Exception { SolrQueryRequest req = req("pt","10.312,-20.556", "sfield","store"); try { assertFuncEquals(req, "geodist()", "geodist($sfield,$pt)", "geodist(store,$pt)", "geodist(field(store),$pt)", "geodist(store,10.312,-20.556)"); } finally { req.close(); } } public void testFuncHsin() throws Exception { assertFuncEquals("hsin(45,true,0,0,45,45)"); } public void testFuncGhhsin() throws Exception { assertFuncEquals("ghhsin(45,point_hash,'asdf')", "ghhsin(45,field(point_hash),'asdf')"); } public void testFuncGeohash() throws Exception { assertFuncEquals("geohash(45,99)"); } public void testFuncDist() throws Exception { assertFuncEquals("dist(2,45,99,101,111)", "dist(2,vector(45,99),vector(101,111))"); } public void testFuncSqedist() throws Exception { assertFuncEquals("sqedist(45,99,101,111)", "sqedist(vector(45,99),vector(101,111))"); } public void testFuncMin() throws Exception { assertFuncEquals("min(5,4,3,2,1)", "min(5, 4, 3, 2, 1)"); assertFuncEquals("min(foo_i,4)", "min(field('foo_i'), 4)"); assertFuncEquals("min(foo_i,sub(4,field('bar_i')))", "min(field(foo_i), sub(4,bar_i))"); } public void testFuncMax() throws Exception { assertFuncEquals("max(5,4,3,2,1)", "max(5, 4, 3, 2, 1)"); assertFuncEquals("max(foo_i,4)", "max(field('foo_i'), 4)"); assertFuncEquals("max(foo_i,sub(4,field('bar_i')))", "max(field(foo_i), sub(4,bar_i))"); } public void testFuncMs() throws Exception { // Note ms() takes in field name, not field(...) assertFuncEquals("ms()", "ms(NOW)"); assertFuncEquals("ms(2000-01-01T00:00:00Z)", "ms('2000-01-01T00:00:00Z')"); assertFuncEquals("ms(myDateField_dt)", "ms('myDateField_dt')"); assertFuncEquals("ms(2000-01-01T00:00:00Z,myDateField_dt)", "ms('2000-01-01T00:00:00Z','myDateField_dt')"); assertFuncEquals("ms(myDateField_dt, NOW)", "ms('myDateField_dt', NOW)"); } public void testFuncMathConsts() throws Exception { assertFuncEquals("pi()"); assertFuncEquals("e()"); } public void testFuncTerms() throws Exception { SolrQueryRequest req = req("myField","field_t","myTerm","my term"); try { for (final String type : new String[]{"docfreq","termfreq", "totaltermfreq","ttf", "idf","tf"}) { // NOTE: these functions takes a field *name* not a field(..) source assertFuncEquals(req, type + "('field_t','my term')", type + "(field_t,'my term')", type + "(field_t,$myTerm)", type + "(field_t,$myTerm)", type + "($myField,$myTerm)"); } // ttf is an alias for totaltermfreq assertFuncEquals(req, "ttf(field_t,'my term')", "ttf('field_t','my term')", "totaltermfreq(field_t,'my term')"); } finally { req.close(); } } public void testFuncSttf() throws Exception { // sttf is an alias for sumtotaltermfreq assertFuncEquals("sttf(foo_t)", "sttf('foo_t')", "sumtotaltermfreq(foo_t)", "sumtotaltermfreq('foo_t')"); assertFuncEquals("sumtotaltermfreq('foo_t')"); } public void testFuncNorm() throws Exception { assertFuncEquals("norm(foo_t)","norm('foo_t')"); } public void testFuncMaxdoc() throws Exception { assertFuncEquals("maxdoc()"); } public void testFuncNumdocs() throws Exception { assertFuncEquals("numdocs()"); } public void testFuncBools() throws Exception { SolrQueryRequest req = req("myTrue","true","myFalse","false"); try { assertFuncEquals(req, "true","$myTrue"); assertFuncEquals(req, "false","$myFalse"); } finally { req.close(); } } public void testFuncExists() throws Exception { SolrQueryRequest req = req("myField","field_t","myQ","asdf"); try { assertFuncEquals(req, "exists(field_t)", "exists($myField)", "exists(field('field_t'))", "exists(field($myField))"); assertFuncEquals(req, "exists(query($myQ))", "exists(query({!lucene v=$myQ}))"); } finally { req.close(); } } public void testFuncNot() throws Exception { SolrQueryRequest req = req("myField","field_b", "myTrue","true"); try { assertFuncEquals(req, "not(true)", "not($myTrue)"); assertFuncEquals(req, "not(not(true))", "not(not($myTrue))"); assertFuncEquals(req, "not(field_b)", "not($myField)", "not(field('field_b'))", "not(field($myField))"); assertFuncEquals(req, "not(exists(field_b))", "not(exists($myField))", "not(exists(field('field_b')))", "not(exists(field($myField)))"); } finally { req.close(); } } public void testFuncDoubleValueBools() throws Exception { SolrQueryRequest req = req("myField","field_b","myTrue","true"); try { for (final String type : new String[]{"and","or","xor"}) { assertFuncEquals(req, type + "(field_b,true)", type + "(field_b,$myTrue)", type + "(field('field_b'),true)", type + "(field($myField),$myTrue)", type + "($myField,$myTrue)"); } } finally { req.close(); } } public void testFuncIf() throws Exception { SolrQueryRequest req = req("myBoolField","foo_b", "myIntField","bar_i", "myTrue","true"); try { assertFuncEquals(req, "if(foo_b,bar_i,25)", "if($myBoolField,bar_i,25)", "if(field('foo_b'),$myIntField,25)", "if(field($myBoolField),field('bar_i'),25)"); assertFuncEquals(req, "if(true,37,field($myIntField))", "if($myTrue,37,$myIntField)"); } finally { req.close(); } } public void testFuncDef() throws Exception { SolrQueryRequest req = req("myField","bar_f"); try { assertFuncEquals(req, "def(bar_f,25)", "def($myField,25)", "def(field('bar_f'),25)"); assertFuncEquals(req, "def(ceil(bar_f),25)", "def(ceil($myField),25)", "def(ceil(field('bar_f')),25)"); } finally { req.close(); } } public void testFuncSingleValueMathFuncs() throws Exception { SolrQueryRequest req = req("myVal","45", "myField","foo_i"); for (final String func : new String[] {"abs","rad","deg","sqrt","cbrt", "log","ln","exp","sin","cos","tan", "asin","acos","atan", "sinh","cosh","tanh", "ceil","floor","rint"}) { try { assertFuncEquals(req, func + "(field(foo_i))", func + "(foo_i)", func + "($myField)"); assertFuncEquals(req, func + "(45)", func+ "($myVal)"); } finally { req.close(); } } } public void testFuncDoubleValueMathFuncs() throws Exception { SolrQueryRequest req = req("myVal","45", "myOtherVal", "27", "myField","foo_i"); for (final String func : new String[] {"pow","hypot","atan2"}) { try { assertFuncEquals(req, func + "(field(foo_i),$myVal)", func+"(foo_i,$myVal)", func + "($myField,45)"); assertFuncEquals(req, func+"(45,$myOtherVal)", func+"($myVal,27)", func+"($myVal,$myOtherVal)"); } finally { req.close(); } } } public void testFuncStrdist() throws Exception { SolrQueryRequest req = req("myVal","zot", "myOtherVal", "yak", "myField","foo_s1"); try { assertFuncEquals(req, "strdist(\"zot\",literal('yak'),edit)", "strdist(literal(\"zot\"),'yak', edit )", "strdist(literal($myVal),literal($myOtherVal),edit)"); assertFuncEquals(req, "strdist(\"zot\",literal($myOtherVal),ngram)", "strdist(\"zot\",'yak', ngram, 2)"); assertFuncEquals(req, "strdist(field('foo_s1'),literal($myOtherVal),jw)", "strdist(field($myField),\"yak\",jw)", "strdist($myField,'yak', jw)"); } finally { req.close(); } } public void testFuncField() throws Exception { assertFuncEquals("field(\"foo_i\")", "field('foo_i\')", "foo_i"); } public void testTestFuncs() throws Exception { assertFuncEquals("sleep(1,5)", "sleep(1,5)"); assertFuncEquals("threadid()", "threadid()"); } /** * this test does not assert anything itself, it simply toggles a static * boolean informing an @AfterClass method to assert that every default * qparser and valuesource parser configured was recorded by * assertQueryEquals and assertFuncEquals. */ public void testParserCoverage() { doAssertParserCoverage = true; } /** * NOTE: defType is not only used to pick the parser, but also to record * the parser being tested for coverage sanity checking * @see #testParserCoverage * @see #assertQueryEquals */ protected void assertQueryEquals(final String defType, final String... inputs) throws Exception { SolrQueryRequest req = req(); try { assertQueryEquals(defType, req, inputs); } finally { req.close(); } } /** * NOTE: defType is not only used to pick the parser, but also to record * the parser being tested for coverage sanity checking * * @see QueryUtils#check * @see QueryUtils#checkEqual * @see #testParserCoverage */ protected void assertQueryEquals(final String defType, final SolrQueryRequest req, final String... inputs) throws Exception { qParsersTested.add(defType); final Query[] queries = new Query[inputs.length]; try { SolrQueryResponse rsp = new SolrQueryResponse(); SolrRequestInfo.setRequestInfo(new SolrRequestInfo(req,rsp)); for (int i = 0; i < inputs.length; i++) { queries[i] = (QParser.getParser(inputs[i], defType, req).getQuery()); } } finally { SolrRequestInfo.clearRequestInfo(); } for (int i = 0; i < queries.length; i++) { QueryUtils.check(queries[i]); // yes starting j=0 is redundent, we're making sure every query // is equal to itself, and that the quality checks work regardless // of which caller/callee is used. for (int j = 0; j < queries.length; j++) { QueryUtils.checkEqual(queries[i], queries[j]); } } } /** * the function name for val parser coverage checking is extracted from * the first input * @see #assertQueryEquals * @see #testParserCoverage */ protected void assertFuncEquals(final String... inputs) throws Exception { SolrQueryRequest req = req(); try { assertFuncEquals(req, inputs); } finally { req.close(); } } /** * the function name for val parser coverage checking is extracted from * the first input * @see #assertQueryEquals * @see #testParserCoverage */ protected void assertFuncEquals(final SolrQueryRequest req, final String... inputs) throws Exception { // pull out the function name final String funcName = (new QueryParsing.StrParser(inputs[0])).getId(); valParsersTested.add(funcName); assertQueryEquals(FunctionQParserPlugin.NAME, req, inputs); } }