diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index 5e2a518ddd8..2d4ce6c93f7 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -260,6 +260,9 @@ Bug Fixes * SOLR-10302: Solr's zkcli scripts now able to find the metrics libraries, which it couldn't earlier (kiran, Ishan Chattopadhyaya) +* SOLR-10283: Learning to Rank (LTR) SolrFeature to reject searches with missing efi (External Feature Information) used by fq. + (Christine Poerschke) + Optimizations ---------------------- diff --git a/solr/contrib/ltr/src/java/org/apache/solr/ltr/feature/SolrFeature.java b/solr/contrib/ltr/src/java/org/apache/solr/ltr/feature/SolrFeature.java index 13eb96fee2a..4aa872d2e7a 100644 --- a/solr/contrib/ltr/src/java/org/apache/solr/ltr/feature/SolrFeature.java +++ b/solr/contrib/ltr/src/java/org/apache/solr/ltr/feature/SolrFeature.java @@ -157,6 +157,9 @@ public class SolrFeature extends Feature { for (String fq : fqs) { if ((fq != null) && (fq.trim().length() != 0)) { fq = macroExpander.expand(fq); + if (fq == null) { + throw new FeatureException(this.getClass().getSimpleName()+" requires efi parameter that was not passed in request."); + } final QParser fqp = QParser.getParser(fq, req); final Query filterQuery = fqp.getQuery(); if (filterQuery != null) { diff --git a/solr/contrib/ltr/src/test-files/featureExamples/external_features.json b/solr/contrib/ltr/src/test-files/featureExamples/external_features.json index d8a9ecae6f9..5c4f12ddaa0 100644 --- a/solr/contrib/ltr/src/test-files/featureExamples/external_features.json +++ b/solr/contrib/ltr/src/test-files/featureExamples/external_features.json @@ -48,4 +48,10 @@ "params" : { "q" : "{!field f=title}${user_query}" } +}, { + "name" : "titlePhrasesMatch", + "class" : "org.apache.solr.ltr.feature.SolrFeature", + "params" : { + "fq" : [ "{!field f=title}${userTitlePhrase1}", "{!field f=title}${userTitlePhrase2}"] + } } ] diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestSelectiveWeightCreation.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestSelectiveWeightCreation.java index 5cfd999b360..7bf8373a56a 100644 --- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestSelectiveWeightCreation.java +++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestSelectiveWeightCreation.java @@ -210,18 +210,22 @@ public class TestSelectiveWeightCreation extends TestRerankBase { @Test public void testSelectiveWeightsRequestFeaturesFromDifferentStore() throws Exception { - final String docs0fv = FeatureLoggerTestUtils.toFeatureVector( + final String docs0fv_sparse = FeatureLoggerTestUtils.toFeatureVector( "matchedTitle","1.0", "titlePhraseMatch","0.6103343"); + final String docs0fv_dense = FeatureLoggerTestUtils.toFeatureVector( + "matchedTitle","1.0", "titlePhraseMatch","0.6103343", "titlePhrasesMatch","0.0"); final String docs0fv_fstore4= FeatureLoggerTestUtils.toFeatureVector( "popularity","3.0", "originalScore","1.0"); + final String docs0fv = chooseDefaultFeatureVector(docs0fv_dense, docs0fv_sparse); + // extract all features in externalmodel's store (default store) // rerank using externalmodel (default store) final SolrQuery query = new SolrQuery(); query.setQuery("*:*"); query.add("fl", "*,score,fv:[fv]"); query.add("rows", "5"); - query.add("rq", "{!ltr reRankDocs=10 model=externalmodel efi.user_query=w3}"); + query.add("rq", "{!ltr reRankDocs=10 model=externalmodel efi.user_query=w3 efi.userTitlePhrase1=w2 efi.userTitlePhrase2=w1}"); assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/id=='3'"); assertJQ("/query" + query.toQueryString(), "/response/docs/[1]/id=='4'"); diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestExternalFeatures.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestExternalFeatures.java index 4010ee1900c..c6ae30fcd58 100644 --- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestExternalFeatures.java +++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestExternalFeatures.java @@ -67,7 +67,7 @@ public class TestExternalFeatures extends TestRerankBase { query.remove("fl"); query.add("fl", "*,score,[fv]"); - query.add("rq", "{!ltr reRankDocs=10 model=externalmodel efi.user_query=w3}"); + query.add("rq", "{!ltr reRankDocs=10 model=externalmodel efi.user_query=w3 efi.userTitlePhrase1=w4 efi.userTitlePhrase2=w5}"); assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/id=='3'"); assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/score==0.7693934"); @@ -77,7 +77,7 @@ public class TestExternalFeatures extends TestRerankBase { // Adding an efi in the transformer should not affect the rq ranking with a // different value for efi of the same parameter query.remove("fl"); - query.add("fl", "*,score,[fv efi.user_query=w2]"); + query.add("fl", "*,score,[fv efi.user_query=w2 efi.userTitlePhrase1=w4 efi.userTitlePhrase2=w5]"); assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/id=='3'"); assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/score==0.7693934"); @@ -92,11 +92,12 @@ public class TestExternalFeatures extends TestRerankBase { query.add("fl", "*,score,fv:[fv]"); query.add("rows", "1"); // Stopword only query passed in - query.add("rq", "{!ltr reRankDocs=10 model=externalmodel efi.user_query='a'}"); + query.add("rq", "{!ltr reRankDocs=10 model=externalmodel efi.user_query='a' efi.userTitlePhrase1='b' efi.userTitlePhrase2='c'}"); final String docs0fv_dense_csv = FeatureLoggerTestUtils.toFeatureVector( "matchedTitle","0.0", - "titlePhraseMatch","0.0"); + "titlePhraseMatch","0.0", + "titlePhrasesMatch","0.0"); final String docs0fv_sparse_csv = FeatureLoggerTestUtils.toFeatureVector(); final String docs0fv_default_csv = chooseDefaultFeatureVector(docs0fv_dense_csv, docs0fv_sparse_csv); @@ -181,4 +182,20 @@ public class TestExternalFeatures extends TestRerankBase { query.add("fl", "fvalias:[fv store=fstore4]"); assertJQ("/query" + query.toQueryString(), "/error/msg=='Exception from createWeight for ValueFeature [name=popularity, params={value=${myPop}, required=true}] ValueFeatureWeight requires efi parameter that was not passed in request.'"); } + + @Test + public void featureExtraction_valueFeatureRequiredInFq_shouldThrowException() throws Exception { + final String userTitlePhrase1 = "userTitlePhrase1"; + final String userTitlePhrase2 = "userTitlePhrase2"; + final String userTitlePhrasePresent = (random().nextBoolean() ? userTitlePhrase1 : userTitlePhrase2); + + final SolrQuery query = new SolrQuery(); + query.setQuery("*:*"); + query.add("rows", "1"); + query.add("fl", "score,features:[fv efi.user_query=uq "+userTitlePhrasePresent+"=utpp]"); + assertJQ("/query" + query.toQueryString(), "/error/msg=='Exception from createWeight for " + + "SolrFeature [name=titlePhrasesMatch, params={fq=[{!field f=title}${"+userTitlePhrase1+"}, {!field f=title}${"+userTitlePhrase2+"}]}] " + + "SolrFeatureWeight requires efi parameter that was not passed in request.'"); + } + }