Author: tommaso
Date: Wed Feb 4 12:08:27 2015
New Revision: 1657132
URL: http://svn.apache.org/r1657132
Log:
OAK-2467 - rep:suggest support for solr index
Added:
jackrabbit/oak/trunk/oak-solr-core/src/test/java/org/apache/jackrabbit/oak/jcr/query/SuggestTest.java
(with props)
Modified:
jackrabbit/oak/trunk/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/FilterQueryParser.java
jackrabbit/oak/trunk/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndex.java
jackrabbit/oak/trunk/oak-solr-core/src/main/resources/solr/oak/conf/schema.xml
jackrabbit/oak/trunk/oak-solr-core/src/main/resources/solr/oak/conf/solrconfig.xml
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/solr/oak/conf/schema.xml
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/solr/oak/conf/solrconfig.xml
Modified:
jackrabbit/oak/trunk/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/FilterQueryParser.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/FilterQueryParser.java?rev=1657132&r1=1657131&r2=1657132&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/FilterQueryParser.java
(original)
+++
jackrabbit/oak/trunk/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/FilterQueryParser.java
Wed Feb 4 12:08:27 2015
@@ -112,6 +112,15 @@ class FilterQueryParser {
// TODO : this should not be always passed
to avoid building the dictionary on each spellcheck request
solrQuery.setParam("spellcheck.build",
true);
}
+ if ("/suggest".equals(requestHandlerString)) {
+ if ("term".equals(kv[0])) {
+ kv[0] = "suggest.q";
+ }
+ solrQuery.setParam("suggest", true);
+
+ // TODO : this should not be always passed
to avoid building the dictionary on each suggest request
+ solrQuery.setParam("suggest.build", true);
+ }
solrQuery.setParam(kv[0], kv[1]);
}
}
@@ -303,7 +312,7 @@ class FilterQueryParser {
private static boolean isSupportedHttpRequest(String nativeQueryString) {
// the query string starts with ${supported-handler.selector}?
- return
nativeQueryString.matches("(spellcheck|mlt|query|select|get)\\\\?.*");
+ return
nativeQueryString.matches("(suggest|spellcheck|mlt|query|select|get)\\\\?.*");
}
private static void setDefaults(SolrQuery solrQuery, OakSolrConfiguration
configuration) {
Modified:
jackrabbit/oak/trunk/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndex.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndex.java?rev=1657132&r1=1657131&r2=1657132&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndex.java
(original)
+++
jackrabbit/oak/trunk/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndex.java
Wed Feb 4 12:08:27 2015
@@ -16,15 +16,18 @@
*/
package org.apache.jackrabbit.oak.plugins.index.solr.query;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.Map;
import java.util.Set;
import javax.annotation.CheckForNull;
import com.google.common.collect.AbstractIterator;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Queues;
import com.google.common.collect.Sets;
import org.apache.jackrabbit.oak.api.PropertyValue;
@@ -50,6 +53,8 @@ import org.apache.solr.client.solrj.resp
import org.apache.solr.client.solrj.response.SpellCheckResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.SimpleOrderedMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -259,18 +264,20 @@ public class SolrQueryIndex implements F
SolrDocumentList docs = queryResponse.getResults();
- onRetrievedDocs(filter, docs);
+ if (docs != null) {
+ onRetrievedDocs(filter, docs);
- if (log.isDebugEnabled()) {
- log.debug("getting docs {}", docs);
- }
+ if (log.isDebugEnabled()) {
+ log.debug("getting docs {}", docs);
+ }
- for (SolrDocument doc : docs) {
- SolrResultRow row = convertToRow(doc);
- if (row != null) {
- queue.add(row);
+ for (SolrDocument doc : docs) {
+ SolrResultRow row = convertToRow(doc);
+ if (row != null) {
+ queue.add(row);
+ }
+ lastDocToRecord = doc;
}
- lastDocToRecord = doc;
}
// handle spellcheck
@@ -279,12 +286,43 @@ public class SolrQueryIndex implements F
spellCheckResponse.getSuggestions().size() >
0) {
SolrDocument fakeDoc = new SolrDocument();
for (SpellCheckResponse.Suggestion suggestion :
spellCheckResponse.getSuggestions()) {
- fakeDoc.addField("rep:spellcheck()",
suggestion.getAlternatives());
+ fakeDoc.addField(QueryImpl.REP_SPELLCHECK,
suggestion.getAlternatives());
}
queue.add(new SolrResultRow("/", 1.0, fakeDoc));
noDocs = true;
}
+ // handle suggest
+ NamedList<Object> response =
queryResponse.getResponse();
+ Map suggest = (Map) response.get("suggest");
+ if (suggest != null) {
+ Set<Map.Entry<String, Object>> suggestEntries =
suggest.entrySet();
+ if (!suggestEntries.isEmpty()) {
+ SolrDocument fakeDoc = new SolrDocument();
+ for (Map.Entry<String, Object> suggestor :
suggestEntries) {
+ SimpleOrderedMap<Object>
suggestionResponses = ((SimpleOrderedMap) suggestor.getValue());
+ for (Map.Entry<String, Object>
suggestionResponse : suggestionResponses) {
+ SimpleOrderedMap<Object>
suggestionResults = ((SimpleOrderedMap) suggestionResponse.getValue());
+ for (Map.Entry<String, Object>
suggestionResult : suggestionResults) {
+ if
("suggestions".equals(suggestionResult.getKey())) {
+
ArrayList<SimpleOrderedMap<Object>> suggestions =
((ArrayList<SimpleOrderedMap<Object>>) suggestionResult.getValue());
+ if (suggestions.isEmpty()) {
+
fakeDoc.addField(QueryImpl.REP_SUGGEST, "[]");
+ }
+ else {
+ for
(SimpleOrderedMap<Object> suggestion : suggestions) {
+
fakeDoc.addField(QueryImpl.REP_SUGGEST, "{term=" + suggestion.get("term") +
",weight=" + suggestion.get("weight") + "}");
+ }
+ }
+ }
+ }
+ }
+ }
+ queue.add(new SolrResultRow("/", 1.0,
fakeDoc));
+ noDocs = true;
+ }
+ }
+
} catch (Exception e) {
if (log.isWarnEnabled()) {
log.warn("query via {} failed.", solrServer, e);
Modified:
jackrabbit/oak/trunk/oak-solr-core/src/main/resources/solr/oak/conf/schema.xml
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-solr-core/src/main/resources/solr/oak/conf/schema.xml?rev=1657132&r1=1657131&r2=1657132&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-solr-core/src/main/resources/solr/oak/conf/schema.xml
(original)
+++
jackrabbit/oak/trunk/oak-solr-core/src/main/resources/solr/oak/conf/schema.xml
Wed Feb 4 12:08:27 2015
@@ -113,11 +113,13 @@
<field name="path_des" type="descendent_path" indexed="true"
stored="false"/>
<field name="catch_all" type="text_general" indexed="true"
stored="false" multiValued="true" termVectors="true"/> <!-- term vectors used
for rep:similar -->
<field name=":path" type="string" indexed="true" stored="false" />
+ <field name=":indexed" type="tdate" indexed="true" stored="false"
default="NOW" docValues="true"/>
+ <field name=":suggest-weight" type="tint" indexed="false"
stored="false" default="1" docValues="true"/>
+ <field name=":suggest" type="string" indexed="true" stored="true"
multiValued="true" />
<field name="_version_" type="long" indexed="true" stored="true"/>
<field name="rep:members" type="string" indexed="true" stored="true"
multiValued="true"/>
<field name="rep:principalName" type="string" indexed="true"
stored="true" multiValued="true"/>
- <field name=":indexed" type="tdate" indexed="true" stored="false"
default="NOW" docValues="numeric"/>
<dynamicField name="*_bin" type="string" indexed="true" stored="true"
multiValued="true"/>
<dynamicField name="*" type="text_general" indexed="true"
stored="true" multiValued="true"/>
</fields>
@@ -127,4 +129,6 @@
<copyField source="path_exact" dest="path_child"/>
<copyField source="path_exact" dest=":path"/>
<copyField source="*" dest="catch_all"/>
+ <copyField source="jcr:title" dest=":suggest"/>
+ <copyField source="jcr:description" dest=":suggest"/>
</schema>
Modified:
jackrabbit/oak/trunk/oak-solr-core/src/main/resources/solr/oak/conf/solrconfig.xml
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-solr-core/src/main/resources/solr/oak/conf/solrconfig.xml?rev=1657132&r1=1657131&r2=1657132&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-solr-core/src/main/resources/solr/oak/conf/solrconfig.xml
(original)
+++
jackrabbit/oak/trunk/oak-solr-core/src/main/resources/solr/oak/conf/solrconfig.xml
Wed Feb 4 12:08:27 2015
@@ -854,6 +854,7 @@
<arr name="last-components">
<str>mlt</str>
<str>spellcheck</str>
+ <str>suggest</str>
</arr>
</requestHandler>
@@ -1100,6 +1101,29 @@
-->
</requestHandler>
+
+ <searchComponent name="suggest" class="solr.SuggestComponent">
+ <lst name="suggester">
+ <str name="name">default</str>
+ <str name="lookupImpl">FuzzyLookupFactory</str>
+ <str name="dictionaryImpl">DocumentDictionaryFactory</str>
+ <str name="field">:suggest</str>
+ <str name="weightField">:suggest-weight</str>
+ <str name="suggestAnalyzerFieldType">string</str>
+ </lst>
+ </searchComponent>
+
+ <requestHandler name="/suggest" class="solr.SearchHandler" startup="lazy">
+ <lst name="defaults">
+ <str name="suggest">true</str>
+ <str name="suggest.count">10</str>
+ </lst>
+ <arr name="components">
+ <str>suggest</str>
+ </arr>
+ </requestHandler>
+
+
<!-- Search Components
Search components are registered to SolrCore and used by
Added:
jackrabbit/oak/trunk/oak-solr-core/src/test/java/org/apache/jackrabbit/oak/jcr/query/SuggestTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-solr-core/src/test/java/org/apache/jackrabbit/oak/jcr/query/SuggestTest.java?rev=1657132&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-solr-core/src/test/java/org/apache/jackrabbit/oak/jcr/query/SuggestTest.java
(added)
+++
jackrabbit/oak/trunk/oak-solr-core/src/test/java/org/apache/jackrabbit/oak/jcr/query/SuggestTest.java
Wed Feb 4 12:08:27 2015
@@ -0,0 +1,100 @@
+/*
+ * 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.
+ */
+package org.apache.jackrabbit.oak.jcr.query;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+import javax.jcr.query.QueryResult;
+import javax.jcr.query.Row;
+import javax.jcr.query.RowIterator;
+
+import org.apache.jackrabbit.core.query.AbstractQueryTest;
+
+/**
+ * Tests the suggest support.
+ */
+public class SuggestTest extends AbstractQueryTest {
+
+ public void testSuggestSql() throws Exception {
+ Session session = superuser;
+ QueryManager qm = session.getWorkspace().getQueryManager();
+ Node n1 = testRootNode.addNode("node1");
+ n1.setProperty("jcr:title", "in 2015 my fox is red, like mike's fox
and john's fox");
+ Node n2 = testRootNode.addNode("node2");
+ n2.setProperty("jcr:title", "in 2015 a red fox is still a fox");
+ session.save();
+
+ String sql = "SELECT [rep:suggest()] FROM nt:base WHERE [jcr:path] =
'/' AND SUGGEST('in 201')";
+ Query q = qm.createQuery(sql, Query.SQL);
+ String result = getResult(q.execute(), "rep:suggest()");
+ assertNotNull(result);
+ assertEquals("[{term=in 2015 a red fox is still a fox,weight=1}, " +
+ "{term=in 2015 my fox is red, like mike's fox and
john's fox,weight=1}]", result);
+ }
+
+ public void testSuggestXPath() throws Exception {
+ Session session = superuser;
+ QueryManager qm = session.getWorkspace().getQueryManager();
+ Node n1 = testRootNode.addNode("node1");
+ n1.setProperty("jcr:title", "in 2015 my fox is red, like mike's fox
and john's fox");
+ Node n2 = testRootNode.addNode("node2");
+ n2.setProperty("jcr:title", "in 2015 a red fox is still a fox");
+ session.save();
+
+ String xpath = "/jcr:root[rep:suggest('in 201')]/(rep:suggest())";
+ Query q = qm.createQuery(xpath, Query.XPATH);
+ String result = getResult(q.execute(), "rep:suggest()");
+ assertNotNull(result);
+ assertEquals("[{term=in 2015 a red fox is still a fox,weight=1}, " +
+ "{term=in 2015 my fox is red, like mike's fox and
john's fox,weight=1}]", result);
+ }
+
+ public void testNoSuggestions() throws Exception {
+ Session session = superuser;
+ QueryManager qm = session.getWorkspace().getQueryManager();
+ Node n1 = testRootNode.addNode("node1");
+ n1.setProperty("jcr:title", "in 2015 my fox is red, like mike's fox
and john's fox");
+ Node n2 = testRootNode.addNode("node2");
+ n2.setProperty("jcr:title", "in 2015 a red fox is still a fox");
+ session.save();
+
+ String sql = "SELECT [rep:suggest()] FROM nt:base WHERE [jcr:path] =
'/' AND SUGGEST('blablabla')";
+ Query q = qm.createQuery(sql, Query.SQL);
+ String result = getResult(q.execute(), "rep:suggest()");
+ assertNotNull(result);
+ assertEquals("[]", result);
+ }
+
+ static String getResult(QueryResult result, String propertyName) throws
RepositoryException {
+ StringBuilder buff = new StringBuilder();
+ RowIterator it = result.getRows();
+ while (it.hasNext()) {
+ if (buff.length() > 0) {
+ buff.append(", ");
+ }
+ Row row = it.nextRow();
+ buff.append(row.getValue(propertyName).getString());
+ }
+ return buff.toString();
+ }
+
+}
\ No newline at end of file
Propchange:
jackrabbit/oak/trunk/oak-solr-core/src/test/java/org/apache/jackrabbit/oak/jcr/query/SuggestTest.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified:
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/solr/oak/conf/schema.xml
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-solr-core/src/test/resources/solr/oak/conf/schema.xml?rev=1657132&r1=1657131&r2=1657132&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/solr/oak/conf/schema.xml
(original)
+++
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/solr/oak/conf/schema.xml
Wed Feb 4 12:08:27 2015
@@ -77,6 +77,14 @@
<filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
</analyzer>
</fieldType>
+ <fieldtype name="phrase_suggest" class="solr.TextField">
+ <analyzer>
+ <tokenizer class="solr.KeywordTokenizerFactory"/>
+ <filter class="solr.PatternReplaceFilterFactory"
pattern="([^\p{L}\p{M}\p{N}\p{Cs}]*[\p{L}\p{M}\p{N}\p{Cs}\_]+:)|([^\p{L}\p{M}\p{N}\p{Cs}])+"
replacement=" " replace="all"/>
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.TrimFilterFactory"/>
+ </analyzer>
+ </fieldtype>
<fieldType name="boolean" class="solr.BoolField"
sortMissingLast="true"/>
@@ -120,9 +128,10 @@
<field name="catch_all" type="text_general" indexed="true"
stored="false" multiValued="true" termVectors="true"/> <!-- term vectors used
for rep:similar -->
<field name="_version_" type="long" indexed="true" stored="true"/>
<field name=":path" type="string" indexed="true" stored="false"/>
- <field name=":indexed" type="tdate" indexed="true" stored="false"
default="NOW" docValues="numeric"/>
+ <field name=":indexed" type="tdate" indexed="true" stored="false"
default="NOW" docValues="true"/>
+ <field name=":suggest-weight" type="tint" indexed="false"
stored="false" default="1" docValues="true"/>
+ <field name=":suggest" type="string" indexed="true" stored="true"
multiValued="true" />
- <field name="jcr:data" type="binary" indexed="true" stored="false"
multiValued="true"/>
<dynamicField name="*_i" type="int" indexed="true" stored="true"/>
<dynamicField name="*_is" type="int" indexed="true" stored="true"
multiValued="true"/>
<dynamicField name="*_s" type="string" indexed="true" stored="true"
/>
@@ -161,4 +170,6 @@
<copyField source="path_exact" dest="path_child"/>
<copyField source="path_exact" dest=":path"/>
<copyField source="*" dest="catch_all"/>
+ <copyField source="jcr:title" dest=":suggest"/>
+ <copyField source="jcr:description" dest=":suggest"/>
</schema>
Modified:
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/solr/oak/conf/solrconfig.xml
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-solr-core/src/test/resources/solr/oak/conf/solrconfig.xml?rev=1657132&r1=1657131&r2=1657132&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/solr/oak/conf/solrconfig.xml
(original)
+++
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/solr/oak/conf/solrconfig.xml
Wed Feb 4 12:08:27 2015
@@ -810,6 +810,7 @@ Lucene will flush based on whichever lim
<arr name="last-components">
<str>mlt</str>
<str>spellcheck</str>
+ <str>suggest</str>
</arr>
</requestHandler>
@@ -1147,6 +1148,27 @@ current implementation relies on the upd
-->
</requestHandler>
+ <searchComponent name="suggest" class="solr.SuggestComponent">
+ <lst name="suggester">
+ <str name="name">default</str>
+ <str name="lookupImpl">FuzzyLookupFactory</str>
+ <str name="dictionaryImpl">DocumentDictionaryFactory</str>
+ <str name="field">:suggest</str>
+ <str name="weightField">:suggest-weight</str>
+ <str name="suggestAnalyzerFieldType">string</str>
+ </lst>
+ </searchComponent>
+
+ <requestHandler name="/suggest" class="solr.SearchHandler" startup="lazy">
+ <lst name="defaults">
+ <str name="suggest">true</str>
+ <str name="suggest.count">10</str>
+ </lst>
+ <arr name="components">
+ <str>suggest</str>
+ </arr>
+ </requestHandler>
+
<!-- Search Components
Search components are registered to SolrCore and used by