Author: thomasm
Date: Thu Feb 27 10:44:08 2014
New Revision: 1572503
URL: http://svn.apache.org/r1572503
Log:
OAK-1325 Support native pass-through queries (e.g. Lucene)
Added:
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/org/
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/org/apache/
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/org/apache/jackrabbit/
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/org/apache/jackrabbit/oak/
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/org/apache/jackrabbit/oak/query/
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/org/apache/jackrabbit/oak/query/native_solr.txt
Modified:
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/test/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrIndexQueryTest.java
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/solr/oak/conf/schema.xml
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=1572503&r1=1572502&r2=1572503&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
Thu Feb 27 10:44:08 2014
@@ -18,13 +18,17 @@ package org.apache.jackrabbit.oak.plugin
import java.util.Collection;
+import javax.annotation.CheckForNull;
+
import org.apache.jackrabbit.oak.api.PropertyValue;
+import org.apache.jackrabbit.oak.plugins.index.aggregate.NodeAggregator;
import
org.apache.jackrabbit.oak.plugins.index.solr.configuration.OakSolrConfiguration;
import org.apache.jackrabbit.oak.spi.query.Cursor;
import org.apache.jackrabbit.oak.spi.query.Filter;
import org.apache.jackrabbit.oak.spi.query.IndexRow;
import org.apache.jackrabbit.oak.spi.query.PropertyValues;
import org.apache.jackrabbit.oak.spi.query.QueryIndex;
+import org.apache.jackrabbit.oak.spi.query.QueryIndex.FulltextQueryIndex;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServer;
@@ -37,19 +41,24 @@ import org.slf4j.LoggerFactory;
/**
* A Solr based {@link QueryIndex}
*/
-public class SolrQueryIndex implements QueryIndex {
+public class SolrQueryIndex implements FulltextQueryIndex {
+ public static final String NATIVE_SOLR_QUERY = "native*solr";
private final Logger log = LoggerFactory.getLogger(SolrQueryIndex.class);
public static final String TYPE = "solr";
private final String name;
private final SolrServer solrServer;
private final OakSolrConfiguration configuration;
+
+ private final NodeAggregator aggregator;
public SolrQueryIndex(String name, SolrServer solrServer,
OakSolrConfiguration configuration) {
this.name = name;
this.solrServer = solrServer;
this.configuration = configuration;
+ // TODO this index should support aggregation in the same way as the
Lucene index
+ this.aggregator = null;
}
@Override
@@ -63,6 +72,21 @@ public class SolrQueryIndex implements Q
return (filter.getPropertyRestrictions() != null ?
filter.getPropertyRestrictions().size() * 0.1 : 0)
+ (filter.getFulltextConditions() != null ?
filter.getFulltextConditions().size() * 0.01 : 0)
+ (filter.getPathRestriction() != null ? 0.2 : 0);
+//
+// FullTextExpression ft = filter.getFullTextConstraint();
+// if (ft == null) {
+// // TODO solr should only be triggered for full-text conditions
+// // return Double.POSITIVE_INFINITY;
+// }
+// int cost = 10;
+// Collection<PropertyRestriction> restrictions =
filter.getPropertyRestrictions();
+// if (restrictions != null) {
+// cost /= 2;
+// }
+// if (filter.getPathRestriction() != null) {
+// cost /= 2;
+// }
+// return cost;
}
@Override
@@ -95,40 +119,45 @@ public class SolrQueryIndex implements Q
Collection<Filter.PropertyRestriction> propertyRestrictions =
filter.getPropertyRestrictions();
if (propertyRestrictions != null && !propertyRestrictions.isEmpty()) {
for (Filter.PropertyRestriction pr : propertyRestrictions) {
- if (pr.propertyName.contains("/")) {
- // cannot handle child-level property restrictions
- continue;
- }
- String first = null;
- if (pr.first != null) {
- first =
partialEscape(String.valueOf(pr.first.getValue(pr.first.getType()))).toString();
- }
- String last = null;
- if (pr.last != null) {
- last =
partialEscape(String.valueOf(pr.last.getValue(pr.last.getType()))).toString();
- }
-
- String prField =
configuration.getFieldForPropertyRestriction(pr);
- CharSequence fieldName = partialEscape(prField != null ?
- prField : pr.propertyName);
- if ("jcr\\:path".equals(fieldName.toString())) {
- queryBuilder.append(configuration.getPathField());
- queryBuilder.append(':');
- queryBuilder.append(first);
+ // native query support
+ if (NATIVE_SOLR_QUERY.equals(pr.propertyName)) {
+
queryBuilder.append(String.valueOf(pr.first.getValue(pr.first.getType())));
} else {
- queryBuilder.append(fieldName).append(':');
- if (pr.first != null && pr.last != null &&
pr.first.equals(pr.last)) {
+ if (pr.propertyName.contains("/")) {
+ // cannot handle child-level property restrictions
+ continue;
+ }
+ String first = null;
+ if (pr.first != null) {
+ first =
partialEscape(String.valueOf(pr.first.getValue(pr.first.getType()))).toString();
+ }
+ String last = null;
+ if (pr.last != null) {
+ last =
partialEscape(String.valueOf(pr.last.getValue(pr.last.getType()))).toString();
+ }
+
+ String prField =
configuration.getFieldForPropertyRestriction(pr);
+ CharSequence fieldName = partialEscape(prField != null ?
+ prField : pr.propertyName);
+ if ("jcr\\:path".equals(fieldName.toString())) {
+ queryBuilder.append(configuration.getPathField());
+ queryBuilder.append(':');
queryBuilder.append(first);
- } else if (pr.first == null && pr.last == null) {
- queryBuilder.append('*');
- } else if ((pr.first != null && pr.last == null) ||
(pr.last != null && pr.first == null) || (!pr.first.equals(pr.last))) {
- // TODO : need to check if this works for all field
types (most likely not!)
- queryBuilder.append(createRangeQuery(first, last,
pr.firstIncluding, pr.lastIncluding));
- } else if (pr.isLike) {
- // TODO : the current parameter substitution is not
expected to work well
-
queryBuilder.append(partialEscape(String.valueOf(pr.first.getValue(pr.first.getType())).replace('%',
'*').replace('_', '?')));
} else {
- throw new RuntimeException("[unexpected!] not handled
case");
+ queryBuilder.append(fieldName).append(':');
+ if (pr.first != null && pr.last != null &&
pr.first.equals(pr.last)) {
+ queryBuilder.append(first);
+ } else if (pr.first == null && pr.last == null) {
+ queryBuilder.append('*');
+ } else if ((pr.first != null && pr.last == null) ||
(pr.last != null && pr.first == null) || (!pr.first.equals(pr.last))) {
+ // TODO : need to check if this works for all
field types (most likely not!)
+ queryBuilder.append(createRangeQuery(first, last,
pr.firstIncluding, pr.lastIncluding));
+ } else if (pr.isLike) {
+ // TODO : the current parameter substitution is
not expected to work well
+
queryBuilder.append(partialEscape(String.valueOf(pr.first.getValue(pr.first.getType())).replace('%',
'*').replace('_', '?')));
+ } else {
+ throw new RuntimeException("[unexpected!] not
handled case");
+ }
}
}
queryBuilder.append(" ");
@@ -139,7 +168,7 @@ public class SolrQueryIndex implements Q
for (String fulltextCondition : fulltextConditions) {
queryBuilder.append(fulltextCondition).append(" ");
}
- if(queryBuilder.length() == 0) {
+ if (queryBuilder.length() == 0) {
queryBuilder.append("*:*");
}
String escapedQuery = queryBuilder.toString();
@@ -198,6 +227,9 @@ public class SolrQueryIndex implements Q
log.debug("sending query {}", query);
}
QueryResponse queryResponse = solrServer.query(query);
+ if (log.isDebugEnabled()) {
+ log.debug("getting response {}", queryResponse);
+ }
cursor = new SolrCursor(queryResponse);
} catch (Exception e) {
throw new RuntimeException(e);
@@ -250,4 +282,12 @@ public class SolrQueryIndex implements Q
}
}
}
+
+
+ @Override
+ @CheckForNull
+ public NodeAggregator getNodeAggregator() {
+ return aggregator;
+ }
+
}
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=1572503&r1=1572502&r2=1572503&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
Thu Feb 27 10:44:08 2014
@@ -104,7 +104,7 @@
</types>
<fields>
<field name="path_exact" type="string" indexed="true" stored="true"/>
- <field name="path_child" type="children_path" indexed="true"
stored="true"/>
+ <field name="path_child" type="children_path" indexed="true"
stored="false"/>
<field name="path_anc" type="parent_path" indexed="true" stored="false"/>
<field name="path_des" type="descendent_path" indexed="true"
stored="false"/>
<field name="ignored" type="ignored" multiValued="true"/>
Modified:
jackrabbit/oak/trunk/oak-solr-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrIndexQueryTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-solr-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrIndexQueryTest.java?rev=1572503&r1=1572502&r2=1572503&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-solr-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrIndexQueryTest.java
(original)
+++
jackrabbit/oak/trunk/oak-solr-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrIndexQueryTest.java
Thu Feb 27 10:44:08 2014
@@ -162,4 +162,66 @@ public class SolrIndexQueryTest extends
assertEquals("/test/resource", strings.next());
assertFalse(strings.hasNext());
}
+
+ @Test
+ public void testNativeSolrQuery() throws Exception {
+ String nativeQueryString = "select [jcr:path] from [nt:base] where
native('solr', 'name:(Hello OR World)')";
+
+ Tree tree = root.getTree("/");
+ Tree test = tree.addChild("test");
+ test.addChild("a").setProperty("name", "Hello");
+ test.addChild("b").setProperty("name", "World");
+ tree.addChild("c");
+ root.commit();
+
+ Iterator<String> strings = executeQuery(nativeQueryString,
"JCR-SQL2").iterator();
+ assertTrue(strings.hasNext());
+ assertEquals("/test/a", strings.next());
+ assertTrue(strings.hasNext());
+ assertEquals("/test/b", strings.next());
+ assertFalse(strings.hasNext());
+ }
+
+ @Test
+ public void testNativeSolrFunctionQuery() throws Exception {
+ String nativeQueryString = "select [jcr:path] from [nt:base] where
native('solr', 'path_child:\\/test _val_:\"recip(rord(name),1,2,3)\"')";
+
+ Tree tree = root.getTree("/");
+ Tree test = tree.addChild("test");
+ test.addChild("a").setProperty("name", "Hello");
+ test.addChild("b").setProperty("name", "World");
+ tree.addChild("c");
+ root.commit();
+
+ Iterator<String> strings = executeQuery(nativeQueryString,
"JCR-SQL2").iterator();
+ assertTrue(strings.hasNext());
+ assertEquals("/test/a", strings.next());
+ assertTrue(strings.hasNext());
+ assertEquals("/test/b", strings.next());
+ assertFalse(strings.hasNext());
+ }
+
+ @Test
+ public void testNativeSolrNestedQuery() throws Exception {
+ String nativeQueryString = "select [jcr:path] from [nt:base] where
native('solr', '_query_:\"{!dismax qf=catch_all q.op=OR}hello world\"')";
+
+ Tree tree = root.getTree("/");
+ Tree test = tree.addChild("test");
+ test.addChild("a").setProperty("name", "Hello");
+ test.addChild("b").setProperty("name", "World");
+ tree.addChild("c");
+ root.commit();
+
+ Iterator<String> strings = executeQuery(nativeQueryString,
"JCR-SQL2").iterator();
+ assertTrue(strings.hasNext());
+ assertEquals("/test/a", strings.next());
+ assertTrue(strings.hasNext());
+ assertEquals("/test/b", strings.next());
+ assertFalse(strings.hasNext());
+ }
+
+ @Test
+ public void nativeSolr() throws Exception {
+ test("native_solr.txt");
+ }
}
Added:
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/org/apache/jackrabbit/oak/query/native_solr.txt
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-solr-core/src/test/resources/org/apache/jackrabbit/oak/query/native_solr.txt?rev=1572503&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/org/apache/jackrabbit/oak/query/native_solr.txt
(added)
+++
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/org/apache/jackrabbit/oak/query/native_solr.txt
Thu Feb 27 10:44:08 2014
@@ -0,0 +1,48 @@
+# 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.
+#
+# Syntax:
+# * lines that start with spaces belong to the previous line
+# * lines starting with "#" are remarks.
+# * lines starting with "select" are queries, followed by expected results and
an empty line
+# * lines starting with "explain" are followed by expected query plan and an
empty line
+# * lines starting with "sql1" are run using the sql1 language
+# * lines starting with "xpath2sql" are just converted from xpath to sql2
+# * all other lines are are committed into the microkernel (line by line)
+# * new tests are typically be added on top, after the syntax docs
+# * use ascii character only
+
+commit / + "test": { "a": { "name": "Hello" }, "b": { "name" : "World" }}
+
+xpath //*[rep:native('solr', 'name:(Hello OR World)')]
+/test/a, null, null
+/test/b, null, null
+
+select [jcr:path] from [nt:base]
+ where native('solr', 'name:(Hello OR World)')
+/test/a
+/test/b
+
+select [jcr:path] from [nt:base] as a
+ where native(a, 'solr', 'path_child:\/test _val_:"recip(rord(name),1,2,3)"')
+/test/a
+/test/b
+
+select [jcr:path] from [nt:base]
+ where native('solr', 'path_child:\/test _val_:"recip(rord(name),1,2,3)"')
+/test/a
+/test/b
+
+
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=1572503&r1=1572502&r2=1572503&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
Thu Feb 27 10:44:08 2014
@@ -104,7 +104,7 @@
</types>
<fields>
<field name="path_exact" type="string" indexed="true" stored="true"/>
- <field name="path_child" type="children_path" indexed="true"
stored="true"/>
+ <field name="path_child" type="children_path" indexed="true"
stored="false"/>
<field name="path_anc" type="parent_path" indexed="true"
stored="false"/>
<field name="path_des" type="descendent_path" indexed="true"
stored="false"/>
<field name="ignored" type="ignored" multiValued="true"/>