From 89510768a2f481d2cfaaa2657eb006b0e6486036 Mon Sep 17 00:00:00 2001
From: Vikas Saurabh <vsaurabh@adobe.com>
Date: Wed, 2 Dec 2015 08:03:48 +0530
Subject: [PATCH] OAK-3352 Expose Lucene search score explanation Use
 oak:explainScore to show explanation of score as calculated by lucene. Filter
 is parsed in the same way as for rep:excerpt()

---
 .../org/apache/jackrabbit/oak/query/QueryImpl.java |  5 +++++
 .../jackrabbit/oak/query/ast/SelectorImpl.java     |  2 ++
 .../plugins/index/lucene/LucenePropertyIndex.java  | 25 +++++++++++++++++-----
 .../index/lucene/LucenePropertyIndexTest.java      | 16 ++++++++++++++
 4 files changed, 43 insertions(+), 5 deletions(-)

diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java
index 3862472..38c245c 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java
@@ -119,6 +119,11 @@ public class QueryImpl implements Query {
     public static final String REP_EXCERPT = "rep:excerpt";
 
     /**
+     * The "oak:explainScore" pseudo-property.
+     */
+    public static final String OAK_EXPLAIN_SCORE = "oak:explainScore";
+
+    /**
      * The "rep:spellcheck" pseudo-property.
      */
     public static final String REP_SPELLCHECK = "rep:spellcheck()";
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java
index 58ba983..877c16f 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java
@@ -661,6 +661,8 @@ public class SelectorImpl extends SourceImpl {
             result = currentRow.getValue(QueryImpl.JCR_SCORE);
         } else if (oakPropertyName.equals(QueryImpl.REP_EXCERPT)) {
             result = currentRow.getValue(QueryImpl.REP_EXCERPT);
+        } else if (oakPropertyName.equals(QueryImpl.OAK_EXPLAIN_SCORE)) {
+            result = currentRow.getValue(QueryImpl.OAK_EXPLAIN_SCORE);
         } else if (oakPropertyName.equals(QueryImpl.REP_SPELLCHECK)) {
             result = currentRow.getValue(QueryImpl.REP_SPELLCHECK);
         } else if (oakPropertyName.equals(QueryImpl.REP_SUGGEST)) {
diff --git a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
index 7670b67..8eac755 100644
--- a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
+++ b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
@@ -299,7 +299,8 @@ public class LucenePropertyIndex implements AdvancedQueryIndex, QueryIndex, Nati
                 return endOfData();
             }
 
-            private LuceneResultRow convertToRow(ScoreDoc doc, IndexSearcher searcher, String excerpt) throws IOException {
+            private LuceneResultRow convertToRow(ScoreDoc doc, IndexSearcher searcher, String excerpt,
+                                                 String explanation) throws IOException {
                 IndexReader reader = searcher.getIndexReader();
                 //TODO Look into usage of field cache for retrieving the path
                 //instead of reading via reader if no of docs in index are limited
@@ -328,7 +329,7 @@ public class LucenePropertyIndex implements AdvancedQueryIndex, QueryIndex, Nati
                     }
 
                     LOG.trace("Matched path {}", path);
-                    return new LuceneResultRow(path, doc.score, excerpt);
+                    return new LuceneResultRow(path, doc.score, excerpt, explanation);
                 }
                 return null;
             }
@@ -382,14 +383,22 @@ public class LucenePropertyIndex implements AdvancedQueryIndex, QueryIndex, Nati
                             PERF_LOGGER.end(start, -1, "{} ...", docs.scoreDocs.length);
                             nextBatchSize = (int) Math.min(nextBatchSize * 2L, 100000);
 
-                            boolean addExcerpt = filter.getQueryStatement() != null && filter.getQueryStatement().contains(QueryImpl.REP_EXCERPT);
+                            String queryStatement = filter.getQueryStatement();
+                            boolean addExcerpt = queryStatement != null && queryStatement.contains(QueryImpl.REP_EXCERPT);
+                            boolean addExplain = queryStatement != null && queryStatement.contains(QueryImpl.OAK_EXPLAIN_SCORE);
+
                             for (ScoreDoc doc : docs.scoreDocs) {
                                 String excerpt = null;
                                 if (addExcerpt) {
                                     excerpt = getExcerpt(indexNode, searcher, query, doc);
                                 }
 
-                                LuceneResultRow row = convertToRow(doc, searcher, excerpt);
+                                String explanation = null;
+                                if (addExplain) {
+                                    explanation = searcher.explain(query, doc.doc).toString();
+                                }
+
+                                LuceneResultRow row = convertToRow(doc, searcher, excerpt, explanation);
                                 if (row != null) {
                                     queue.add(row);
                                 }
@@ -1324,8 +1333,10 @@ public class LucenePropertyIndex implements AdvancedQueryIndex, QueryIndex, Nati
         final String suggestion;
         final boolean isVirutal;
         final String excerpt;
+        final String explanation;
 
-        LuceneResultRow(String path, double score, String excerpt) {
+        LuceneResultRow(String path, double score, String excerpt, String explanation) {
+            this.explanation = explanation;
             this.excerpt = excerpt;
             this.isVirutal = false;
             this.path = path;
@@ -1339,6 +1350,7 @@ public class LucenePropertyIndex implements AdvancedQueryIndex, QueryIndex, Nati
             this.score = weight;
             this.suggestion = suggestion;
             this.excerpt = null;
+            this.explanation = null;
         }
 
         LuceneResultRow(String suggestion) {
@@ -1428,6 +1440,9 @@ public class LucenePropertyIndex implements AdvancedQueryIndex, QueryIndex, Nati
                     if (QueryImpl.REP_SPELLCHECK.equals(columnName) || QueryImpl.REP_SUGGEST.equals(columnName)) {
                         return PropertyValues.newString(currentRow.suggestion);
                     }
+                    if (QueryImpl.OAK_EXPLAIN_SCORE.equals(columnName)) {
+                        return PropertyValues.newString(currentRow.explanation);
+                    }
                     if (QueryImpl.REP_EXCERPT.equals(columnName)) {
                         return PropertyValues.newString(currentRow.excerpt);
                     }
diff --git a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java
index 3a825e9..d188df3 100644
--- a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java
+++ b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java
@@ -432,6 +432,22 @@ public class LucenePropertyIndexTest extends AbstractQueryTest {
         assertQuery("select [jcr:path] from [nt:base] where propa is not null", asList("/test/a", "/test/b"));
     }
 
+    @Test
+    public void explainScoreTest() throws Exception {
+        Tree idx = createIndex("test1", of("propa"));
+        idx.addChild(PROP_NODE).addChild("propa");
+        root.commit();
+
+        Tree test = root.getTree("/").addChild("test");
+        test.addChild("a").setProperty("propa", "a");
+        root.commit();
+
+        String query = "select [oak:explainScore] from [nt:base] where propa='a'";
+        List<String> result = executeQuery(query, SQL2, false, false);
+        assertEquals(1, result.size());
+        assertTrue(result.get(0).contains("(MATCH)"));
+    }
+
     //OAK-2568
     @Test
     public void multiValueAnd() throws Exception{
-- 
2.5.3

