You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by th...@apache.org on 2013/07/23 11:01:20 UTC
svn commit: r1505940 - in /jackrabbit/oak/trunk:
oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/
oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/
oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/
oak-core/src/mai...
Author: thomasm
Date: Tue Jul 23 09:01:20 2013
New Revision: 1505940
URL: http://svn.apache.org/r1505940
Log:
OAK-890 Query: advanced fulltext search conditions (disabled for now)
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextVisitor.java
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/MultiLuceneIndex.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AndImpl.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ConstraintImpl.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/OrImpl.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextAnd.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextExpression.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextOr.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextParser.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextTerm.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Filter.java
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryFulltextTest.java
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryFulltextTest.java
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AndImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AndImpl.java?rev=1505940&r1=1505939&r2=1505940&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AndImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AndImpl.java Tue Jul 23 09:01:20 2013
@@ -18,10 +18,13 @@
*/
package org.apache.jackrabbit.oak.query.ast;
+import java.util.ArrayList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import org.apache.jackrabbit.oak.query.fulltext.FullTextAnd;
+import org.apache.jackrabbit.oak.query.fulltext.FullTextExpression;
import org.apache.jackrabbit.oak.query.index.FilterImpl;
import com.google.common.collect.Maps;
@@ -67,6 +70,21 @@ public class AndImpl extends ConstraintI
}
@Override
+ public FullTextExpression getFullTextConstraint(SelectorImpl s) {
+ FullTextExpression f1 = constraint1.getFullTextConstraint(s);
+ FullTextExpression f2 = constraint2.getFullTextConstraint(s);
+ if (f1 == null) {
+ return f2;
+ } else if (f2 == null) {
+ return f1;
+ }
+ ArrayList<FullTextExpression> list = new ArrayList<FullTextExpression>();
+ list.add(f1);
+ list.add(f2);
+ return new FullTextAnd(list);
+ }
+
+ @Override
public Set<SelectorImpl> getSelectors() {
Set<SelectorImpl> s1 = constraint1.getSelectors();
Set<SelectorImpl> s2 = constraint1.getSelectors();
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ConstraintImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ConstraintImpl.java?rev=1505940&r1=1505939&r2=1505940&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ConstraintImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ConstraintImpl.java Tue Jul 23 09:01:20 2013
@@ -19,6 +19,7 @@ package org.apache.jackrabbit.oak.query.
import java.util.Map;
import java.util.Set;
+import org.apache.jackrabbit.oak.query.fulltext.FullTextExpression;
import org.apache.jackrabbit.oak.query.index.FilterImpl;
/**
@@ -55,6 +56,21 @@ public abstract class ConstraintImpl ext
public abstract Set<PropertyExistenceImpl> getPropertyExistenceConditions();
/**
+ * Get the (combined) full-text constraint. For constraints of the form
+ * "contains(*, 'x') or contains(*, 'y')", the combined expression is
+ * returned. If there is none, null is returned. For constraints of the form
+ * "contains(*, 'x') or z=1", null is returned as the full-text index cannot
+ * be used in this case for filtering (as it might filter out the z=1
+ * nodes).
+ *
+ * @param s the selector
+ * @return the full-text constraint, if there is any, or null if not
+ */
+ protected FullTextExpression getFullTextConstraint(SelectorImpl s) {
+ return null;
+ }
+
+ /**
* Get the set of selectors for the given condition.
*
* @return the set of selectors (possibly empty)
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java?rev=1505940&r1=1505939&r2=1505940&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java Tue Jul 23 09:01:20 2013
@@ -41,6 +41,29 @@ import org.apache.jackrabbit.oak.spi.que
* A fulltext "contains(...)" condition.
*/
public class FullTextSearchImpl extends ConstraintImpl {
+
+ /**
+ * Feature flag.
+ * Disabled until OAK-890 is fully implemented.
+ * Enable for testing OAK-890 related changes.
+ */
+ public static final boolean OAK_890_ADVANCED_FT_SEARCH = false;
+
+ /**
+ * Compatibility for Jackrabbit 2.0 single quoted phrase queries.
+ * (contains(., "word ''hello world'' word")
+ * These are queries that delimit a phrase with a single quote
+ * instead, as in the spec, using double quotes.
+ */
+ public static final boolean JACKRABBIT_2_SINGLE_QUOTED_PHRASE = true;
+
+ /**
+ * Compatibility for Jackrabbit 2.0 queries with ampersand.
+ * (contains(., "max&moritz"))
+ * The ampersand is converted to a space, and a search is made for the
+ * two words "max" and "moritz" (not a phrase search).
+ */
+ public static final boolean JACKRABBIT_2_AMPERSAND_TO_SPACE = true;
private final String selectorName;
private final String relativePath;
@@ -64,10 +87,12 @@ public class FullTextSearchImpl extends
propertyName = propertyName.substring(slash + 1);
}
- // temporary workaround to support using an index for
- // "contains(a/*, 'x') or contains(a/a, x') or contains(a/b, 'x')"
- // TODO this behavior does not match the specification
- propertyName = null;
+ if (!OAK_890_ADVANCED_FT_SEARCH) {
+ // temporary workaround to support using an index for
+ // "contains(a/*, 'x') or contains(a/a, x') or contains(a/b, 'x')"
+ // TODO this behavior does not match the specification
+ propertyName = null;
+ }
if (propertyName == null || "*".equals(propertyName)) {
this.propertyName = null;
@@ -118,6 +143,28 @@ public class FullTextSearchImpl extends
}
@Override
+ public FullTextExpression getFullTextConstraint(SelectorImpl s) {
+ if (s != selector) {
+ return null;
+ }
+ PropertyValue v = fullTextSearchExpression.currentValue();
+ try {
+ String p = propertyName;
+ if (OAK_890_ADVANCED_FT_SEARCH) {
+ if (relativePath != null) {
+ if (p == null) {
+ p = "*";
+ }
+ p = PathUtils.concat(relativePath, p);
+ }
+ }
+ return FullTextParser.parse(p, v.getValue(Type.STRING));
+ } catch (ParseException e) {
+ throw new IllegalArgumentException("Invalid expression: " + fullTextSearchExpression, e);
+ }
+ }
+
+ @Override
public Set<SelectorImpl> getSelectors() {
return Collections.singleton(selector);
}
@@ -129,11 +176,17 @@ public class FullTextSearchImpl extends
@Override
public boolean evaluate() {
- // disable evaluation if a fulltext index is used, as
- // we don't know what exact options are used in the fulltext index
- // (stop word, special characters,...)
- if (selector.index instanceof FulltextQueryIndex) {
- return true;
+ if (OAK_890_ADVANCED_FT_SEARCH) {
+ // the LuceneIndex implementation doesn't currently support
+ // queries that search for words *within* fields; the
+ // field value must match exactly
+ } else {
+ // disable evaluation if a fulltext index is used, as
+ // we don't know what exact options are used in the fulltext index
+ // (stop word, special characters,...)
+ if (selector.index instanceof FulltextQueryIndex) {
+ return true;
+ }
}
StringBuilder buff = new StringBuilder();
@@ -166,15 +219,9 @@ public class FullTextSearchImpl extends
}
}
}
- PropertyValue v = fullTextSearchExpression.currentValue();
- try {
- FullTextExpression expr = FullTextParser.parse(propertyName, v.getValue(Type.STRING));
- return expr.evaluate(buff.toString());
- } catch (ParseException e) {
- throw new IllegalArgumentException("Invalid expression: " + fullTextSearchExpression, e);
- }
+ return getFullTextConstraint(selector).evaluate(buff.toString());
}
-
+
private static void appendString(StringBuilder buff, PropertyValue p) {
if (p.isArray()) {
for (String v : p.getValue(STRINGS)) {
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/OrImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/OrImpl.java?rev=1505940&r1=1505939&r2=1505940&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/OrImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/OrImpl.java Tue Jul 23 09:01:20 2013
@@ -18,10 +18,13 @@
*/
package org.apache.jackrabbit.oak.query.ast;
+import java.util.ArrayList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import org.apache.jackrabbit.oak.query.fulltext.FullTextExpression;
+import org.apache.jackrabbit.oak.query.fulltext.FullTextOr;
import org.apache.jackrabbit.oak.query.index.FilterImpl;
import com.google.common.collect.Lists;
@@ -74,6 +77,21 @@ public class OrImpl extends ConstraintIm
}
@Override
+ public FullTextExpression getFullTextConstraint(SelectorImpl s) {
+ FullTextExpression f1 = constraint1.getFullTextConstraint(s);
+ FullTextExpression f2 = constraint2.getFullTextConstraint(s);
+ if (f1 == null || f2 == null) {
+ // the full-text index can not be used for conditions of the form
+ // "contains(a, 'x') or b=123"
+ return null;
+ }
+ ArrayList<FullTextExpression> list = new ArrayList<FullTextExpression>();
+ list.add(f1);
+ list.add(f2);
+ return new FullTextOr(list);
+ }
+
+ @Override
public Set<SelectorImpl> getSelectors() {
Set<SelectorImpl> s1 = constraint1.getSelectors();
Set<SelectorImpl> s2 = constraint1.getSelectors();
@@ -124,11 +142,10 @@ public class OrImpl extends ConstraintIm
@Override
public void restrict(FilterImpl f) {
Set<PropertyExistenceImpl> set = getPropertyExistenceConditions();
- if (set.isEmpty()) {
- return;
- }
- for (PropertyExistenceImpl p : set) {
- p.restrict(f);
+ if (!set.isEmpty()) {
+ for (PropertyExistenceImpl p : set) {
+ p.restrict(f);
+ }
}
}
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java?rev=1505940&r1=1505939&r2=1505940&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java Tue Jul 23 09:01:20 2013
@@ -42,6 +42,7 @@ import org.apache.jackrabbit.oak.api.Pro
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.query.QueryImpl;
+import org.apache.jackrabbit.oak.query.fulltext.FullTextExpression;
import org.apache.jackrabbit.oak.query.index.FilterImpl;
import org.apache.jackrabbit.oak.spi.query.Cursor;
import org.apache.jackrabbit.oak.spi.query.Cursors;
@@ -220,6 +221,8 @@ public class SelectorImpl extends Source
// (".. is null" must be written as "not .. is not null").
if (queryConstraint != null) {
queryConstraint.restrict(f);
+ FullTextExpression ft = queryConstraint.getFullTextConstraint(this);
+ f.setFullTextConstraint(ft);
}
return f;
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextAnd.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextAnd.java?rev=1505940&r1=1505939&r2=1505940&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextAnd.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextAnd.java Tue Jul 23 09:01:20 2013
@@ -19,7 +19,7 @@
package org.apache.jackrabbit.oak.query.fulltext;
import java.util.ArrayList;
-import java.util.LinkedHashSet;
+import java.util.Set;
/**
* A fulltext "and" condition.
@@ -44,16 +44,13 @@ public class FullTextAnd extends FullTex
@Override
public FullTextExpression simplify() {
- // remove duplicates
- LinkedHashSet<FullTextExpression> newList = new LinkedHashSet<FullTextExpression>();
- for (int i = 0; i < list.size(); i++) {
- newList.add(list.get(i).simplify());
+ Set<FullTextExpression> set = FullTextOr.getSortedAndUniqueSet(list);
+ if (set.size() == 1) {
+ return set.iterator().next();
}
- if (newList.size() == 1) {
- return newList.iterator().next();
- }
- ArrayList<FullTextExpression> l = new ArrayList<FullTextExpression>(newList.size());
- l.addAll(newList);
+ ArrayList<FullTextExpression> l = new ArrayList<FullTextExpression>(
+ set.size());
+ l.addAll(set);
return new FullTextAnd(l);
}
@@ -80,5 +77,10 @@ public class FullTextAnd extends FullTex
public int getPrecedence() {
return PRECEDENCE_AND;
}
+
+ @Override
+ public boolean accept(FullTextVisitor v) {
+ return v.visit(this);
+ }
}
\ No newline at end of file
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextExpression.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextExpression.java?rev=1505940&r1=1505939&r2=1505940&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextExpression.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextExpression.java Tue Jul 23 09:01:20 2013
@@ -81,4 +81,12 @@ public abstract class FullTextExpression
return toString().hashCode();
}
+ /**
+ * Let the expression call the applicable visit method of the visitor.
+ *
+ * @param v the visitor
+ * @return true if the visit method returned true
+ */
+ public abstract boolean accept(FullTextVisitor v);
+
}
\ No newline at end of file
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextOr.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextOr.java?rev=1505940&r1=1505939&r2=1505940&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextOr.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextOr.java Tue Jul 23 09:01:20 2013
@@ -19,7 +19,10 @@
package org.apache.jackrabbit.oak.query.fulltext;
import java.util.ArrayList;
-import java.util.LinkedHashSet;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
/**
* A fulltext "or" condition.
@@ -44,18 +47,33 @@ public class FullTextOr extends FullText
@Override
public FullTextExpression simplify() {
- // remove duplicates
- LinkedHashSet<FullTextExpression> newList = new LinkedHashSet<FullTextExpression>();
- for (int i = 0; i < list.size(); i++) {
- newList.add(list.get(i).simplify());
- }
- if (newList.size() == 1) {
- return newList.iterator().next();
+ Set<FullTextExpression> set = getSortedAndUniqueSet(list);
+ if (set.size() == 1) {
+ return set.iterator().next();
}
- ArrayList<FullTextExpression> l = new ArrayList<FullTextExpression>(newList.size());
- l.addAll(newList);
+ ArrayList<FullTextExpression> l = new ArrayList<FullTextExpression>(
+ set.size());
+ l.addAll(set);
return new FullTextOr(l);
}
+
+ static Set<FullTextExpression> getSortedAndUniqueSet(
+ List<FullTextExpression> list) {
+ // sort and remove duplicates
+ TreeSet<FullTextExpression> set = new TreeSet<FullTextExpression>(
+ new Comparator<FullTextExpression>() {
+
+ @Override
+ public int compare(FullTextExpression o1,
+ FullTextExpression o2) {
+ return o1.toString().compareTo(o2.toString());
+ }
+ });
+ for (int i = 0; i < list.size(); i++) {
+ set.add(list.get(i).simplify());
+ }
+ return set;
+ }
@Override
public String toString() {
@@ -80,5 +98,10 @@ public class FullTextOr extends FullText
public int getPrecedence() {
return PRECEDENCE_OR;
}
+
+ @Override
+ public boolean accept(FullTextVisitor v) {
+ return v.visit(this);
+ }
}
\ No newline at end of file
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextParser.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextParser.java?rev=1505940&r1=1505939&r2=1505940&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextParser.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextParser.java Tue Jul 23 09:01:20 2013
@@ -21,6 +21,8 @@ package org.apache.jackrabbit.oak.query.
import java.text.ParseException;
import java.util.ArrayList;
+import org.apache.jackrabbit.oak.query.ast.FullTextSearchImpl;
+
/**
* A parser for fulltext condition literals. The grammar is defined in the
@@ -43,6 +45,11 @@ public class FullTextParser {
public static FullTextExpression parse(String propertyName, String text) throws ParseException {
FullTextParser p = new FullTextParser();
p.propertyName = propertyName;
+ if (FullTextSearchImpl.JACKRABBIT_2_AMPERSAND_TO_SPACE) {
+ if (text.indexOf('&') >= 0) {
+ text = text.replace('&', ' ');
+ }
+ }
p.text = text;
FullTextExpression e = p.parseOr();
return e;
@@ -119,6 +126,35 @@ public class FullTextParser {
buff.append(c);
}
}
+ } else if (c == '\'' && FullTextSearchImpl.JACKRABBIT_2_SINGLE_QUOTED_PHRASE) {
+ // basically the same as double quote
+ parseIndex++;
+ while (true) {
+ if (parseIndex >= text.length()) {
+ throw getSyntaxError("single quote");
+ }
+ c = text.charAt(parseIndex++);
+ if (c == '\\') {
+ escaped = true;
+ if (parseIndex >= text.length()) {
+ throw getSyntaxError("escaped char");
+ }
+ c = text.charAt(parseIndex++);
+ buff.append(c);
+ } else if (c == '\'') {
+ if (parseIndex < text.length()) {
+ if (text.charAt(parseIndex) == '^') {
+ boost = "";
+ } else if (text.charAt(parseIndex) != ' ') {
+ throw getSyntaxError("space");
+ }
+ }
+ parseIndex++;
+ break;
+ } else {
+ buff.append(c);
+ }
+ }
} else {
do {
c = text.charAt(parseIndex++);
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextTerm.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextTerm.java?rev=1505940&r1=1505939&r2=1505940&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextTerm.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextTerm.java Tue Jul 23 09:01:20 2013
@@ -96,9 +96,7 @@ public class FullTextTerm extends FullTe
if (not) {
buff.append('-');
}
- if (propertyName != null && "*".equals(propertyName)) {
- // TODO support property name conditions
- // (currently disabled)
+ if (propertyName != null && !"*".equals(propertyName)) {
buff.append(propertyName).append(':');
}
buff.append('\"');
@@ -122,9 +120,26 @@ public class FullTextTerm extends FullTe
return propertyName;
}
+ public String getBoost() {
+ return boost;
+ }
+
+ public boolean isNot() {
+ return not;
+ }
+
+ public String getText() {
+ return text;
+ }
+
@Override
public int getPrecedence() {
return PRECEDENCE_TERM;
}
+
+ @Override
+ public boolean accept(FullTextVisitor v) {
+ return v.visit(this);
+ }
}
\ No newline at end of file
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextVisitor.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextVisitor.java?rev=1505940&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextVisitor.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/fulltext/FullTextVisitor.java Tue Jul 23 09:01:20 2013
@@ -0,0 +1,79 @@
+/*
+ * 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.query.fulltext;
+
+/**
+ * A visitor for full-text expressions. This class is abstract because at least
+ * one of the methods needs to be implemented to make anything useful, most
+ * likely visit(FullTextTerm).
+ */
+public interface FullTextVisitor {
+
+ /**
+ * Visit an "and" expression.
+ *
+ * @param and the "and" expression
+ * @return true if visiting should continue
+ */
+ boolean visit(FullTextAnd and);
+
+ /**
+ * Visit an "or" expression.
+ *
+ * @param or the "or" expression
+ * @return true if visiting should continue
+ */
+ boolean visit(FullTextOr or);
+
+ /**
+ * Visit a term
+ *
+ * @param term the term
+ * @return true if visiting should continue
+ */
+ boolean visit(FullTextTerm term);
+
+ /**
+ * The base implementation of a full-text visitor.
+ */
+ public abstract static class FullTextVisitorBase implements FullTextVisitor {
+
+ @Override
+ public boolean visit(FullTextAnd and) {
+ for (FullTextExpression e : and.list) {
+ if (!e.accept(this)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean visit(FullTextOr or) {
+ for (FullTextExpression e : or.list) {
+ if (!e.accept(this)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ }
+
+}
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java?rev=1505940&r1=1505939&r2=1505940&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java Tue Jul 23 09:01:20 2013
@@ -34,6 +34,7 @@ import org.apache.jackrabbit.oak.api.Pro
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.query.ast.Operator;
import org.apache.jackrabbit.oak.query.ast.SelectorImpl;
+import org.apache.jackrabbit.oak.query.fulltext.FullTextExpression;
import org.apache.jackrabbit.oak.spi.query.Filter;
/**
@@ -64,6 +65,8 @@ public class FilterImpl implements Filte
* The fulltext search conditions, if any.
*/
private final ArrayList<String> fulltextConditions = new ArrayList<String>();
+
+ private FullTextExpression fullTextConstraint;
private final HashMap<String, PropertyRestriction> propertyRestrictions =
new HashMap<String, PropertyRestriction>();
@@ -298,6 +301,9 @@ public class FilterImpl implements Filte
if (queryStatement != null) {
buff.append("query=").append(queryStatement);
}
+ if (fullTextConstraint != null) {
+ buff.append("fullText=").append(fullTextConstraint);
+ }
buff.append(", path=").append(path).append(pathRestriction);
if (!propertyRestrictions.isEmpty()) {
buff.append(", property=[");
@@ -442,6 +448,15 @@ public class FilterImpl implements Filte
public void restrictFulltextCondition(String condition) {
fulltextConditions.add(condition);
}
+
+ public void setFullTextConstraint(FullTextExpression constraint) {
+ this.fullTextConstraint = constraint;
+ }
+
+ @Override
+ public FullTextExpression getFullTextConstraint() {
+ return fullTextConstraint;
+ }
@Override
@Nullable
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Filter.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Filter.java?rev=1505940&r1=1505939&r2=1505940&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Filter.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Filter.java Tue Jul 23 09:01:20 2013
@@ -27,6 +27,7 @@ import javax.annotation.Nullable;
import javax.jcr.PropertyType;
import org.apache.jackrabbit.oak.api.PropertyValue;
+import org.apache.jackrabbit.oak.query.fulltext.FullTextExpression;
/**
* The filter for an index lookup that contains a number of restrictions that
@@ -54,6 +55,13 @@ public interface Filter {
* @return the conditions (an empty collection if not used)
*/
Collection<String> getFulltextConditions();
+
+ /**
+ * Get the fulltext search condition expression, if any.
+ *
+ * @return the condition (null if none)
+ */
+ FullTextExpression getFullTextConstraint();
/**
* Get the property restriction for the given property, if any.
Modified: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryFulltextTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryFulltextTest.java?rev=1505940&r1=1505939&r2=1505940&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryFulltextTest.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryFulltextTest.java Tue Jul 23 09:01:20 2013
@@ -30,6 +30,7 @@ import javax.jcr.query.RowIterator;
import org.apache.jackrabbit.oak.jcr.AbstractRepositoryTest;
import org.apache.jackrabbit.oak.jcr.NodeStoreFixture;
+import org.apache.jackrabbit.oak.query.ast.FullTextSearchImpl;
import org.junit.Test;
/**
@@ -60,9 +61,15 @@ public class QueryFulltextTest extends A
Query q;
q = qm.createQuery("explain " + sql2, Query.JCR_SQL2);
- assertEquals("[nt:base] as [nt:base] /* traverse \"*\" " +
- "where contains([nt:base].[*], cast('hello OR hallo' as string)) */",
- getResult(q.execute(), "plan"));
+ if (FullTextSearchImpl.OAK_890_ADVANCED_FT_SEARCH) {
+ assertEquals("[nt:base] as [nt:base] /* traverse \"*\" " +
+ "where contains([nt:base].[text], cast('hello OR hallo' as string)) */",
+ getResult(q.execute(), "plan"));
+ } else {
+ assertEquals("[nt:base] as [nt:base] /* traverse \"*\" " +
+ "where contains([nt:base].[*], cast('hello OR hallo' as string)) */",
+ getResult(q.execute(), "plan"));
+ }
// verify the result
// uppercase "OR" mean logical "or"
Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java?rev=1505940&r1=1505939&r2=1505940&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java Tue Jul 23 09:01:20 2013
@@ -33,6 +33,7 @@ import static org.apache.jackrabbit.oak.
import static org.apache.jackrabbit.oak.plugins.index.lucene.TermFactory.newPathTerm;
import static org.apache.jackrabbit.oak.query.QueryImpl.JCR_PATH;
import static org.apache.lucene.search.BooleanClause.Occur.MUST;
+import static org.apache.lucene.search.BooleanClause.Occur.MUST_NOT;
import static org.apache.lucene.search.BooleanClause.Occur.SHOULD;
import java.io.File;
@@ -40,11 +41,20 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.query.ast.FullTextSearchImpl;
+import org.apache.jackrabbit.oak.query.fulltext.FullTextAnd;
+import org.apache.jackrabbit.oak.query.fulltext.FullTextExpression;
+import org.apache.jackrabbit.oak.query.fulltext.FullTextOr;
+import org.apache.jackrabbit.oak.query.fulltext.FullTextTerm;
+import org.apache.jackrabbit.oak.query.fulltext.FullTextVisitor;
import org.apache.jackrabbit.oak.spi.query.Cursor;
import org.apache.jackrabbit.oak.spi.query.Cursors;
import org.apache.jackrabbit.oak.spi.query.Filter;
@@ -58,6 +68,7 @@ import org.apache.lucene.index.Directory
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.MultiFields;
import org.apache.lucene.index.Term;
+import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery;
@@ -126,6 +137,83 @@ public class LuceneIndex implements Full
@Override
public double getCost(Filter filter, NodeState root) {
+ if (!FullTextSearchImpl.OAK_890_ADVANCED_FT_SEARCH) {
+ return getCostOld(filter, root);
+ }
+ if (!isLive(root)) {
+ // unusable index
+ return Double.POSITIVE_INFINITY;
+ }
+ FullTextExpression ft = filter.getFullTextConstraint();
+ if (ft == null) {
+ // no full-text condition: don't use this index,
+ // as there might be a better one
+ return Double.POSITIVE_INFINITY;
+ }
+ Set<String> relPaths = getRelativePaths(ft);
+ if (relPaths.size() > 1) {
+ // there are multiple "parents", as in
+ // "contains(a/x, 'hello') and contains(b/x, 'world')"
+ return new MultiLuceneIndex(filter, root, relPaths).getCost();
+ }
+ String parent = relPaths.iterator().next();
+ if (parent.isEmpty()) {
+ // no relative properties
+ return 10;
+ }
+ // all relative properties have the same "parent", as in
+ // "contains(a/x, 'hello') and contains(a/y, 'world')" or
+ // "contains(a/x, 'hello') or contains(a/*, 'world')"
+ // TODO: proper cost calculation
+ // we assume this will cause more read operations,
+ // as we need to read the node and then the parent
+ return 15;
+ }
+
+ /**
+ * Get the set of relative paths of a full-text condition. For example, for
+ * the condition "contains(a/b, 'hello') and contains(c/d, 'world'), the set
+ * { "a", "c" } is returned. If there are no relative properties, then one entry
+ * is returned. If there is no expression, then an empty set is returned.
+ *
+ * @param ft the full-text expression
+ * @return the set of relative paths (possibly empty)
+ */
+ private static Set<String> getRelativePaths(FullTextExpression ft) {
+ if (ft == null) {
+ // there might be no full-text constraint when using the
+ // LowCostLuceneIndexProvider which is used for testing
+ // TODO if the LowCostLuceneIndexProvider is removed, we should do
+ // the following instead:
+
+ // throw new IllegalStateException("Lucene index is used even when no full-text conditions are used for filter " + filter);
+
+ return Collections.emptySet();
+ }
+ final HashSet<String> relPaths = new HashSet<String>();
+ ft.accept(new FullTextVisitor.FullTextVisitorBase() {
+
+ @Override
+ public boolean visit(FullTextTerm term) {
+ String p = term.getPropertyName();
+ if (p == null) {
+ relPaths.add("");
+ } else if (p.startsWith("../") || p.startsWith("./")) {
+ throw new IllegalArgumentException("Relative parent is not supported:" + p);
+ } else if (PathUtils.getDepth(p) > 1) {
+ String parent = PathUtils.getParentPath(p);
+ relPaths.add(parent);
+ } else {
+ relPaths.add("");
+ }
+ return true;
+ }
+
+ });
+ return relPaths;
+ }
+
+ private double getCostOld(Filter filter, NodeState root) {
// TODO: proper cost calculation
if (!isLive(root)) {
// unusable index
@@ -211,11 +299,102 @@ public class LuceneIndex implements Full
@Override
public String getPlan(Filter filter, NodeState root) {
- return getQuery(filter, root, null).toString();
+ if (FullTextSearchImpl.OAK_890_ADVANCED_FT_SEARCH) {
+ FullTextExpression ft = filter.getFullTextConstraint();
+ Set<String> relPaths = getRelativePaths(ft);
+ if (relPaths.size() > 1) {
+ return new MultiLuceneIndex(filter, root, relPaths).getPlan();
+ }
+ String parent = relPaths.size() == 0 ? "" : relPaths.iterator().next();
+ // we only restrict non-full-text conditions if there is
+ // no relative property in the full-text constraint
+ boolean nonFullTextConstraints = parent.isEmpty();
+ String plan = getQuery(filter, null, nonFullTextConstraints) + " ft:(" + ft + ")";
+ if (!parent.isEmpty()) {
+ plan += " parent:" + parent;
+ }
+ return plan;
+ }
+ return getQueryOld(filter, null).toString();
}
@Override
public Cursor query(Filter filter, NodeState root) {
+ if (!FullTextSearchImpl.OAK_890_ADVANCED_FT_SEARCH) {
+ return queryOld(filter, root);
+ }
+ if (!isLive(root)) {
+ throw new IllegalStateException("Lucene index is not live");
+ }
+ FullTextExpression ft = filter.getFullTextConstraint();
+ Set<String> relPaths = getRelativePaths(ft);
+ if (relPaths.size() > 1) {
+ return new MultiLuceneIndex(filter, root, relPaths).query();
+ }
+ String parent = relPaths.size() == 0 ? "" : relPaths.iterator().next();
+ // we only restrict non-full-text conditions if there is
+ // no relative property in the full-text constraint
+ boolean nonFullTextConstraints = parent.isEmpty();
+ Directory directory = newDirectory(root);
+ if (directory == null) {
+ return Cursors.newPathCursor(Collections.<String> emptySet());
+ }
+ long s = System.currentTimeMillis();
+ try {
+ try {
+ IndexReader reader = DirectoryReader.open(directory);
+ try {
+ IndexSearcher searcher = new IndexSearcher(reader);
+ Collection<String> paths = new ArrayList<String>();
+ HashSet<String> seenPaths = new HashSet<String>();
+ Query query = getQuery(filter, reader, nonFullTextConstraints);
+ if (query != null) {
+ TopDocs docs = searcher
+ .search(query, Integer.MAX_VALUE);
+ for (ScoreDoc doc : docs.scoreDocs) {
+ String path = reader.document(doc.doc,
+ PATH_SELECTOR).get(PATH);
+ if (path != null) {
+ if ("".equals(path)) {
+ path = "/";
+ }
+ if (!parent.isEmpty()) {
+ // ensure the path ends with the given
+ // relative path
+ if (!path.endsWith("/" + parent)) {
+ continue;
+ }
+ // get the base path
+ for (int i = 0, size = PathUtils
+ .getDepth(parent); i < size; i++) {
+ path = PathUtils.getParentPath(path);
+ }
+ // avoid duplicate entries
+ if (seenPaths.contains(path)) {
+ continue;
+ }
+ seenPaths.add(path);
+ }
+ paths.add(path);
+ }
+ }
+ }
+ LOG.debug("query via {} took {} ms.", this,
+ System.currentTimeMillis() - s);
+ return Cursors.newPathCursor(paths);
+ } finally {
+ reader.close();
+ }
+ } finally {
+ directory.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ return Cursors.newPathCursor(Collections.<String> emptySet());
+ }
+ }
+
+ private Cursor queryOld(Filter filter, NodeState root) {
Directory directory = newDirectory(root);
if (directory == null) {
return Cursors.newPathCursor(Collections.<String> emptySet());
@@ -228,7 +407,7 @@ public class LuceneIndex implements Full
IndexSearcher searcher = new IndexSearcher(reader);
Collection<String> paths = new ArrayList<String>();
- Query query = getQuery(filter, root, reader);
+ Query query = getQueryOld(filter, reader);
if (query != null) {
TopDocs docs = searcher
.search(query, Integer.MAX_VALUE);
@@ -256,11 +435,51 @@ public class LuceneIndex implements Full
return Cursors.newPathCursor(Collections.<String> emptySet());
}
}
-
- private static Query getQuery(Filter filter, NodeState root,
- IndexReader reader) {
+
+ private static Query getQuery(Filter filter, IndexReader reader, boolean nonFullTextConstraints) {
List<Query> qs = new ArrayList<Query>();
-
+ FullTextExpression ft = filter.getFullTextConstraint();
+ if (ft == null) {
+ // there might be no full-text constraint
+ // when using the LowCostLuceneIndexProvider
+ // which is used for testing
+ } else {
+ qs.add(getFullTextQuery(ft));
+ }
+ if (nonFullTextConstraints) {
+ addNonFullTextConstraints(qs, filter, reader);
+ }
+ if (qs.size() == 0) {
+ return new MatchAllDocsQuery();
+ }
+ if (qs.size() == 1) {
+ return qs.get(0);
+ }
+ BooleanQuery bq = new BooleanQuery();
+ for (Query q : qs) {
+ bq.add(q, MUST);
+ }
+ return bq;
+ }
+
+ private static Query getQueryOld(Filter filter, IndexReader reader) {
+ List<Query> qs = new ArrayList<Query>();
+ addNonFullTextConstraints(qs, filter, reader);
+ addFullTextConstraintsOld(qs, filter);
+ if (qs.size() == 0) {
+ return new MatchAllDocsQuery();
+ }
+ if (qs.size() == 1) {
+ return qs.get(0);
+ }
+ BooleanQuery bq = new BooleanQuery();
+ for (Query q : qs) {
+ bq.add(q, MUST);
+ }
+ return bq;
+ }
+
+ private static void addNonFullTextConstraints(List<Query> qs, Filter filter, IndexReader reader) {
if (!filter.matchesAllTypes()) {
addNodeTypeConstraints(qs, filter);
}
@@ -289,9 +508,12 @@ public class LuceneIndex implements Full
case PARENT:
if (PathUtils.denotesRoot(path)) {
// there's no parent of the root node
- return null;
+ // we add a path that can not possibly occur because there
+ // is no way to say "match no documents" in Lucene
+ qs.add(new TermQuery(new Term(FieldNames.PATH, "///")));
+ } else {
+ qs.add(new TermQuery(newPathTerm(PathUtils.getParentPath(path))));
}
- qs.add(new TermQuery(newPathTerm(PathUtils.getParentPath(path))));
break;
case NO_RESTRICTION:
break;
@@ -363,23 +585,9 @@ public class LuceneIndex implements Full
qs.add(TermRangeQuery.newStringRange(name, first, last,
pr.firstIncluding, pr.lastIncluding));
- }
-
- addFulltextConstraints(qs, filter);
-
- if (qs.size() == 0) {
- return new MatchAllDocsQuery();
- }
- if (qs.size() == 1) {
- return qs.get(0);
- }
- BooleanQuery bq = new BooleanQuery();
- for (Query q : qs) {
- bq.add(q, MUST);
- }
- return bq;
+ }
}
-
+
private static void addReferenceConstraint(String uuid, List<Query> qs,
IndexReader reader) {
if (reader == null) {
@@ -407,8 +615,85 @@ public class LuceneIndex implements Full
}
qs.add(bq);
}
+
+ static Query getFullTextQuery(FullTextExpression ft) {
+ // a reference to the query, so it can be set in the visitor
+ // (a "non-local return")
+ final AtomicReference<Query> result = new AtomicReference<Query>();
+ ft.accept(new FullTextVisitor() {
+
+ @Override
+ public boolean visit(FullTextOr or) {
+ BooleanQuery q = new BooleanQuery();
+ q.setMinimumNumberShouldMatch(1);
+ for (FullTextExpression e : or.list) {
+ Query x = getFullTextQuery(e);
+ q.add(x, SHOULD);
+ }
+ result.set(q);
+ return true;
+ }
+
+ @Override
+ public boolean visit(FullTextAnd and) {
+ BooleanQuery q = new BooleanQuery();
+ for (FullTextExpression e : and.list) {
+ Query x = getFullTextQuery(e);
+ // Lucene can't deal with "must(must_not(x))"
+ if (x instanceof BooleanQuery) {
+ BooleanQuery bq = (BooleanQuery) x;
+ for (BooleanClause c : bq.clauses()) {
+ q.add(c);
+ }
+ } else {
+ q.add(x, MUST);
+ }
+ }
+ result.set(q);
+ return true;
+ }
- private static void addFulltextConstraints(List<Query> qs, Filter filter) {
+ @Override
+ public boolean visit(FullTextTerm term) {
+ Query q;
+ String p = term.getPropertyName();
+ if (p != null && p.indexOf('/') >= 0) {
+ p = PathUtils.getName(p);
+ }
+ String text = term.getText();
+ if (text.indexOf(' ') >= 0) {
+ PhraseQuery pq = new PhraseQuery();
+ for (String t : text.split(" ")) {
+ pq.add(newFulltextTerm(t));
+ }
+ q = pq;
+ } else {
+ // q = new TermQuery(newFulltextTerm(text));
+ if (!text.endsWith("*")) {
+ text = text + "*";
+ }
+ text = text.toLowerCase();
+ q = new WildcardQuery(newFulltextTerm(text));
+ }
+ String boost = term.getBoost();
+ if (boost != null) {
+ q.setBoost(Float.parseFloat(boost));
+ }
+ if (term.isNot()) {
+ BooleanQuery bq = new BooleanQuery();
+ bq.add(q, MUST_NOT);
+ result.set(bq);
+ } else {
+ result.set(q);
+ }
+ return true;
+ }
+
+ });
+ return result.get();
+ }
+
+ private static void addFullTextConstraintsOld(List<Query> qs, Filter filter) {
if (filter.getFulltextConditions() == null
|| filter.getFulltextConditions().isEmpty()) {
return;
Added: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/MultiLuceneIndex.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/MultiLuceneIndex.java?rev=1505940&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/MultiLuceneIndex.java (added)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/MultiLuceneIndex.java Tue Jul 23 09:01:20 2013
@@ -0,0 +1,56 @@
+/*
+ * 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.plugins.index.lucene;
+
+import java.util.Set;
+
+import org.apache.jackrabbit.oak.spi.query.Cursor;
+import org.apache.jackrabbit.oak.spi.query.Filter;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+/**
+ * A full-text search that is used when the query contains there are multiple
+ * "parents", as in "contains(a/x, 'hello') and contains(b/x, 'world')". In this
+ * case multiple Lucene queries are run, and the result is combined.
+ */
+public class MultiLuceneIndex {
+
+ private final Filter filter;
+ private final NodeState root;
+ private final Set<String> relPaths;
+
+ MultiLuceneIndex(Filter filter, NodeState root,
+ Set<String> relPaths) {
+ this.filter = filter;
+ this.root = root;
+ this.relPaths = relPaths;
+ }
+
+ public String getPlan() {
+ return "Not yet implemented";
+ }
+
+ public double getCost() {
+ return Double.POSITIVE_INFINITY;
+ }
+
+ public Cursor query() {
+ throw new IllegalStateException(
+ "Queries that use multiple relative properties is not yet supported");
+ }
+
+}
Modified: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryFulltextTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryFulltextTest.java?rev=1505940&r1=1505939&r2=1505940&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryFulltextTest.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryFulltextTest.java Tue Jul 23 09:01:20 2013
@@ -27,6 +27,7 @@ import javax.jcr.query.QueryResult;
import javax.jcr.query.RowIterator;
import org.apache.jackrabbit.core.query.AbstractQueryTest;
+import org.apache.jackrabbit.oak.query.ast.FullTextSearchImpl;
/**
* Tests the fulltext index.
@@ -50,16 +51,33 @@ public class QueryFulltextTest extends A
Query q;
q = qm.createQuery("explain " + sql2, Query.JCR_SQL2);
- assertEquals("[nt:base] as [nt:base] /* +:fulltext:hello +:fulltext:or +:fulltext:hallo " +
- "where contains([nt:base].[*], cast('hello OR hallo' as string)) */",
- getResult(q.execute(), "plan"));
-
- // verify the result
- // uppercase "OR" mean logical "or"
- q = qm.createQuery(sql2, Query.JCR_SQL2);
- // TODO OAK-902
- // assertEquals("/testroot/node1, /testroot/node2, /testroot/node3",
- // getResult(q.execute(), "path"));
+ if (FullTextSearchImpl.OAK_890_ADVANCED_FT_SEARCH) {
+ // TODO the plan should actually be:
+// assertEquals("[nt:base] as [nt:base] /* " +
+// "+((text:hallo text:hello)~1) +text:{* TO *} " +
+// "ft:(text:\"hallo\" OR text:\"hello\") " +
+// "where contains([nt:base].[text], cast('hello OR hallo' as string)) */",
+// getResult(q.execute(), "plan"));
+ assertEquals("[nt:base] as [nt:base] /* " +
+ "+((:fulltext:hallo* :fulltext:hello*)~1) +text:{* TO *} " +
+ "ft:(text:\"hallo\" OR text:\"hello\") " +
+ "where contains([nt:base].[text], cast('hello OR hallo' as string)) */",
+ getResult(q.execute(), "plan"));
+ } else {
+ assertEquals("[nt:base] as [nt:base] /* " +
+ "+:fulltext:hello +:fulltext:or +:fulltext:hallo " +
+ "where contains([nt:base].[*], cast('hello OR hallo' as string)) */",
+ getResult(q.execute(), "plan"));
+
+ // verify the result
+ // uppercase "OR" mean logical "or"
+ q = qm.createQuery(sql2, Query.JCR_SQL2);
+ if (FullTextSearchImpl.OAK_890_ADVANCED_FT_SEARCH) {
+ assertEquals("/testroot/node1, /testroot/node2, /testroot/node3",
+ getResult(q.execute(), "path"));
+ }
+
+ }
// lowercase "or" mean search for the term "or"
sql2 = "select [jcr:path] as [path] from [nt:base] " +
@@ -75,17 +93,92 @@ public class QueryFulltextTest extends A
QueryManager qm = session.getWorkspace().getQueryManager();
Node n1 = testRootNode.addNode("node1");
n1.setProperty("text", "hello");
+ Node n2 = testRootNode.addNode("node2");
+ n2.setProperty("text", "hallo");
+ Node n3 = testRootNode.addNode("node3");
+ n3.setProperty("text", "hello hallo");
session.save();
-
+
+ Query q;
+
String sql2 = "select [jcr:path] as [path] from [nt:base] " +
- "where contains([node1/text], 'hello') order by [jcr:path]";
+ "where ISCHILDNODE([/testroot])" +
+ " AND CONTAINS(text, 'hallo')";
+ if (FullTextSearchImpl.OAK_890_ADVANCED_FT_SEARCH) {
+ q = qm.createQuery("explain " + sql2, Query.JCR_SQL2);
+ // TODO the plan should actually be:
+// assertEquals("[nt:base] as [nt:base] /* " +
+// "+text:hallo +:path:/testroot/* +text:{* TO *} " +
+// "ft:(text:\"hallo\") " +
+// "where (ischildnode([nt:base], [/testroot])) " +
+// "and (contains([nt:base].[text], cast('hallo' as string))) */",
+// getResult(q.execute(), "plan"));
+ assertEquals("[nt:base] as [nt:base] /* " +
+ "+:fulltext:hallo* +:path:/testroot/* +text:{* TO *} " +
+ "ft:(text:\"hallo\") " +
+ "where (ischildnode([nt:base], [/testroot])) " +
+ "and (contains([nt:base].[text], cast('hallo' as string))) */",
+ getResult(q.execute(), "plan"));
+ }
- Query q;
+ q = qm.createQuery(sql2, Query.JCR_SQL2);
+ assertEquals("/testroot/node2, /testroot/node3", getResult(q.execute(), "path"));
+
+ sql2 = "select [jcr:path] as [path] from [nt:base] " +
+ "where contains([node1/text], 'hello') order by [jcr:path]";
q = qm.createQuery(sql2, Query.JCR_SQL2);
- // TODO OAK-913
- // assertEquals("/testroot", getResult(q.execute(), "path"));
-
+ if (FullTextSearchImpl.OAK_890_ADVANCED_FT_SEARCH) {
+ assertEquals("/testroot", getResult(q.execute(), "path"));
+ }
+
+ sql2 = "select [jcr:path] as [path] from [nt:base] " +
+ "where contains([node2/text], 'hello OR hallo') order by [jcr:path]";
+ q = qm.createQuery("explain " + sql2, Query.JCR_SQL2);
+ if (FullTextSearchImpl.OAK_890_ADVANCED_FT_SEARCH) {
+ // TODO the plan should actually be:
+// assertEquals("[nt:base] as [nt:base] /* " +
+// "(text:hallo text:hello)~1 " +
+// "ft:(node2/text:\"hallo\" OR node2/text:\"hello\") " +
+// "parent:node2 " +
+// "where contains([nt:base].[node2/text], cast('hello OR hallo' as string)) */",
+// getResult(q.execute(), "plan"));
+ assertEquals("[nt:base] as [nt:base] /* " +
+ "(:fulltext:hallo* :fulltext:hello*)~1 " +
+ "ft:(node2/text:\"hallo\" OR node2/text:\"hello\") " +
+ "parent:node2 " +
+ "where contains([nt:base].[node2/text], cast('hello OR hallo' as string)) */",
+ getResult(q.execute(), "plan"));
+ q = qm.createQuery(sql2, Query.JCR_SQL2);
+ assertEquals("/testroot",
+ getResult(q.execute(), "path"));
+ } else {
+ assertEquals("[nt:base] as [nt:base] /* " +
+ "+:fulltext:hello +:fulltext:or +:fulltext:hallo " +
+ "where contains([nt:base].[node2/*], cast('hello OR hallo' as string)) */",
+ getResult(q.execute(), "plan"));
+ q = qm.createQuery(sql2, Query.JCR_SQL2);
+ assertEquals("",
+ getResult(q.execute(), "path"));
+ }
+
+ sql2 = "select [jcr:path] as [path] from [nt:base] " +
+ "where contains([node1/text], 'hello') " +
+ "and contains([node2/text], 'hallo') " +
+ "order by [jcr:path]";
+ q = qm.createQuery("explain " + sql2, Query.JCR_SQL2);
+ if (FullTextSearchImpl.OAK_890_ADVANCED_FT_SEARCH) {
+ // TODO OAK-890
+ assertEquals("[nt:base] as [nt:base] /* " +
+ "Not yet implemented " +
+ "where (contains([nt:base].[node1/text], cast('hello' as string))) " +
+ "and (contains([nt:base].[node2/text], cast('hallo' as string))) */",
+ getResult(q.execute(), "plan"));
+// q = qm.createQuery(sql2, Query.JCR_SQL2);
+// assertEquals("/testroot",
+// getResult(q.execute(), "path"));
+ }
+
}
static String getResult(QueryResult result, String propertyName) throws RepositoryException {