You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by mr...@apache.org on 2010/11/02 14:32:44 UTC
svn commit: r1030038 - in /jackrabbit/trunk:
jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/
jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/
jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/ ja...
Author: mreutegg
Date: Tue Nov 2 13:32:43 2010
New Revision: 1030038
URL: http://svn.apache.org/viewvc?rev=1030038&view=rev
Log:
JCR-2790: jcr:like on node name
Added:
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardNameQuery.java (with props)
Modified:
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardQuery.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardTermEnum.java
jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/GQLTest.java
jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/FnNameQueryTest.java
jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/query/GQL.java
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java?rev=1030038&r1=1030037&r2=1030038&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java Tue Nov 2 13:32:43 2010
@@ -44,6 +44,7 @@ import org.apache.jackrabbit.spi.PathFac
import org.apache.jackrabbit.spi.commons.conversion.NameException;
import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
import org.apache.jackrabbit.spi.commons.query.AndQueryNode;
import org.apache.jackrabbit.spi.commons.query.DefaultQueryNodeVisitor;
@@ -69,9 +70,7 @@ import org.apache.lucene.analysis.Analyz
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
-import org.apache.lucene.search.BooleanClause;
-import org.apache.lucene.search.BooleanQuery;
-import org.apache.lucene.search.Query;
+import org.apache.lucene.search.*;
import org.apache.lucene.search.BooleanClause.Occur;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -100,6 +99,11 @@ public class LuceneQueryBuilder implemen
private static final Name PARENT_ELEMENT_NAME = PATH_FACTORY.getParentElement().getName();
/**
+ * Name constant for fn:name()
+ */
+ private static final Name FN_NAME = NameFactoryImpl.getInstance().create(SearchManager.NS_FN_URI, "name()");
+
+ /**
* Root node of the abstract query tree
*/
private final QueryRootNode root;
@@ -709,35 +713,44 @@ public class LuceneQueryBuilder implemen
}
// support for fn:name()
- if (propertyName.getNamespaceURI().equals(SearchManager.NS_FN_URI)
- && propertyName.getLocalName().equals("name()")) {
+ if (propertyName.equals(FN_NAME)) {
if (node.getValueType() != QueryConstants.TYPE_STRING) {
exceptions.add(new InvalidQueryException("Name function can "
+ "only be used in conjunction with a string literal"));
return data;
}
- if (node.getOperation() != QueryConstants.OPERATION_EQ_VALUE
- && node.getOperation() != QueryConstants.OPERATION_EQ_GENERAL) {
- exceptions.add(new InvalidQueryException("Name function can "
- + "only be used in conjunction with an equals operator"));
- return data;
- }
- // check if string literal is a valid XML Name
- if (XMLChar.isValidName(node.getStringValue())) {
- // parse string literal as JCR Name
- try {
- Name n = session.getQName(ISO9075.decode(node.getStringValue()));
- query = new NameQuery(n, indexFormatVersion, nsMappings);
- } catch (NameException e) {
- exceptions.add(e);
- return data;
- } catch (NamespaceException e) {
- exceptions.add(e);
- return data;
+ if (node.getOperation() == QueryConstants.OPERATION_EQ_VALUE
+ || node.getOperation() == QueryConstants.OPERATION_EQ_GENERAL) {
+ // check if string literal is a valid XML Name
+ if (XMLChar.isValidName(node.getStringValue())) {
+ // parse string literal as JCR Name
+ try {
+ Name n = session.getQName(ISO9075.decode(node.getStringValue()));
+ query = new NameQuery(n, indexFormatVersion, nsMappings);
+ } catch (NameException e) {
+ exceptions.add(e);
+ return data;
+ } catch (NamespaceException e) {
+ exceptions.add(e);
+ return data;
+ }
+ } else {
+ // will never match -> create dummy query
+ query = new BooleanQuery();
+ }
+ } else if (node.getOperation() == QueryConstants.OPERATION_LIKE) {
+ // the like operation always has one string value.
+ // no coercing, see above
+ if (stringValues[0].equals("%")) {
+ query = new org.apache.lucene.search.MatchAllDocsQuery();
+ } else {
+ query = new WildcardNameQuery(stringValues[0],
+ transform[0], session, nsMappings);
}
} else {
- // will never match -> create dummy query
- query = new BooleanQuery();
+ exceptions.add(new InvalidQueryException("Name function can "
+ + "only be used in conjunction with the following operators: equals, like"));
+ return data;
}
} else {
switch (node.getOperation()) {
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java?rev=1030038&r1=1030037&r2=1030038&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java Tue Nov 2 13:32:43 2010
@@ -306,6 +306,8 @@ public abstract class QueryResultImpl im
log.warn("Unable to close query result: " + e);
}
}
+ // make sure PerQueryCache is disposed
+ PerQueryCache.getInstance().dispose();
}
}
Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardNameQuery.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardNameQuery.java?rev=1030038&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardNameQuery.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardNameQuery.java Tue Nov 2 13:32:43 2010
@@ -0,0 +1,84 @@
+/*
+ * 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.core.query.lucene;
+
+import java.io.IOException;
+
+import javax.jcr.NamespaceException;
+
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.FilteredTermEnum;
+import org.apache.lucene.search.MultiTermQuery;
+import org.apache.lucene.search.Query;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implements a wildcard query on the name field.
+ * <p/>
+ * Wildcards are:
+ * <ul>
+ * <li><code>%</code> : matches zero or more characters</li>
+ * <li><code>_</code> : matches exactly one character</li>
+ * </ul>
+ * Wildcards in the namespace prefix are not supported and will not match.
+ */
+public class WildcardNameQuery extends WildcardQuery {
+
+ private static final long serialVersionUID = -4705104992551930918L;
+
+ /**
+ * Logger instance for this class.
+ */
+ private static final Logger log = LoggerFactory.getLogger(WildcardNameQuery.class);
+
+ public WildcardNameQuery(String pattern,
+ int transform,
+ NamespaceResolver resolver,
+ NamespaceMappings nsMappings) {
+ super(FieldNames.LABEL, null,
+ convertPattern(pattern, resolver, nsMappings), transform);
+ }
+
+ private static String convertPattern(String pattern,
+ NamespaceResolver resolver,
+ NamespaceMappings nsMappings) {
+ String prefix = "";
+ int idx = pattern.indexOf(':');
+ if (idx != -1) {
+ prefix = pattern.substring(0, idx);
+ }
+ StringBuffer sb = new StringBuffer();
+ // translate prefix
+ try {
+ sb.append(nsMappings.getPrefix(resolver.getURI(prefix)));
+ } catch (NamespaceException e) {
+ // prefix in pattern is probably unknown
+ log.debug("unknown namespace prefix in pattern: " + pattern);
+ // -> ignore and use empty string for index internal prefix
+ // this will not match anything
+ }
+ sb.append(":");
+ // remaining pattern, may also be whole pattern
+ sb.append(pattern.substring(idx + 1));
+ return sb.toString();
+ }
+}
Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardNameQuery.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardQuery.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardQuery.java?rev=1030038&r1=1030037&r2=1030038&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardQuery.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardQuery.java Tue Nov 2 13:32:43 2010
@@ -61,9 +61,9 @@ public class WildcardQuery extends Query
private final String field;
/**
- * Name of the property to search.
+ * Creates a term value for a given string.
*/
- private final String propName;
+ private final WildcardTermEnum.TermValueFactory tvf;
/**
* The wildcard pattern.
@@ -71,7 +71,7 @@ public class WildcardQuery extends Query
private final String pattern;
/**
- * How property values are tranformed before they are matched using the
+ * How property values are transformed before they are matched using the
* provided pattern.
*/
private int transform = TRANSFORM_NONE;
@@ -91,11 +91,20 @@ public class WildcardQuery extends Query
* @param transform how property values are transformed before they are
* matched using the <code>pattern</code>.
*/
- public WildcardQuery(String field, String propName, String pattern, int transform) {
+ public WildcardQuery(String field, final String propName, String pattern, int transform) {
this.field = field.intern();
- this.propName = propName;
this.pattern = pattern;
this.transform = transform;
+ if (propName != null) {
+ tvf = new WildcardTermEnum.TermValueFactory() {
+ @Override
+ public String createValue(String s) {
+ return FieldNames.createNamedValue(propName, s);
+ }
+ };
+ } else {
+ tvf = new WildcardTermEnum.TermValueFactory();
+ }
}
/**
@@ -128,7 +137,7 @@ public class WildcardQuery extends Query
public Query rewrite(IndexReader reader) throws IOException {
Query stdWildcardQuery = new MultiTermQuery(new Term(field, pattern)) {
protected FilteredTermEnum getEnum(IndexReader reader) throws IOException {
- return new WildcardTermEnum(reader, field, propName, pattern, transform);
+ return new WildcardTermEnum(reader, field, tvf, pattern, transform);
}
};
try {
@@ -158,7 +167,7 @@ public class WildcardQuery extends Query
* @return a string representation of this query.
*/
public String toString(String field) {
- return propName + ":" + pattern;
+ return field + ":" + tvf.createValue(pattern);
}
/**
@@ -277,7 +286,7 @@ public class WildcardQuery extends Query
WildcardQueryScorer(Similarity similarity, IndexReader reader) {
super(similarity);
this.reader = reader;
- this.cacheKey = field + '\uFFFF' + propName + '\uFFFF' + transform + '\uFFFF' + pattern;
+ this.cacheKey = field + '\uFFFF' + tvf.createValue('\uFFFF' + pattern) + '\uFFFF' + transform;
// check cache
PerQueryCache cache = PerQueryCache.getInstance();
Map<String, BitSet> m = (Map<String, BitSet>) cache.get(WildcardQueryScorer.class, reader);
@@ -344,7 +353,7 @@ public class WildcardQuery extends Query
if (hitsCalculated) {
return;
}
- TermEnum terms = new WildcardTermEnum(reader, field, propName, pattern, transform);
+ TermEnum terms = new WildcardTermEnum(reader, field, tvf, pattern, transform);
try {
// use unpositioned TermDocs
TermDocs docs = reader.termDocs();
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardTermEnum.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardTermEnum.java?rev=1030038&r1=1030037&r2=1030038&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardTermEnum.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardTermEnum.java Tue Nov 2 13:32:43 2010
@@ -46,6 +46,11 @@ class WildcardTermEnum extends FilteredT
private final String field;
/**
+ * Factory for term values.
+ */
+ private final TermValueFactory tvf;
+
+ /**
* The term prefix without wildcards
*/
private final String prefix;
@@ -70,8 +75,7 @@ class WildcardTermEnum extends FilteredT
*
* @param reader the index reader.
* @param field the lucene field to search.
- * @param propName the embedded jcr property name or <code>null</code> if
- * there is not embedded property name.
+ * @param tvf the term value factory.
* @param pattern the pattern to match the values.
* @param transform the transformation that should be applied to the term
* enum from the index reader.
@@ -82,7 +86,7 @@ class WildcardTermEnum extends FilteredT
*/
public WildcardTermEnum(IndexReader reader,
String field,
- String propName,
+ TermValueFactory tvf,
String pattern,
int transform) throws IOException {
if (transform < TRANSFORM_NONE || transform > TRANSFORM_UPPER_CASE) {
@@ -90,6 +94,7 @@ class WildcardTermEnum extends FilteredT
}
this.field = field;
this.transform = transform;
+ this.tvf = tvf;
int idx = 0;
@@ -97,17 +102,13 @@ class WildcardTermEnum extends FilteredT
// optimize the term comparison by removing the prefix from the pattern
// and therefore use a more precise range scan
while (idx < pattern.length()
- && Character.isLetterOrDigit(pattern.charAt(idx))) {
+ && (Character.isLetterOrDigit(pattern.charAt(idx)) || pattern.charAt(idx) == ':')) {
idx++;
}
- if (propName == null) {
- prefix = pattern.substring(0, idx);
- } else {
- prefix = FieldNames.createNamedValue(propName, pattern.substring(0, idx));
- }
+ prefix = tvf.createValue(pattern.substring(0, idx));
} else {
- prefix = FieldNames.createNamedValue(propName, "");
+ prefix = tvf.createValue("");
}
// initialize with prefix as dummy value
@@ -117,7 +118,7 @@ class WildcardTermEnum extends FilteredT
if (transform == TRANSFORM_NONE) {
setEnum(reader.terms(new Term(field, prefix)));
} else {
- setEnum(new LowerUpperCaseTermEnum(reader, field, propName, pattern, transform));
+ setEnum(new LowerUpperCaseTermEnum(reader, field, pattern, transform));
}
}
@@ -172,7 +173,6 @@ class WildcardTermEnum extends FilteredT
public LowerUpperCaseTermEnum(IndexReader reader,
String field,
- String propName,
String pattern,
int transform) throws IOException {
if (transform != TRANSFORM_LOWER_CASE && transform != TRANSFORM_UPPER_CASE) {
@@ -201,28 +201,28 @@ class WildcardTermEnum extends FilteredT
String patternPrefix = pattern.substring(0, idx);
if (patternPrefix.length() == 0) {
// scan full property range
- String prefix = FieldNames.createNamedValue(propName, "");
- String limit = FieldNames.createNamedValue(propName, "\uFFFF");
+ String prefix = tvf.createValue("");
+ String limit = tvf.createValue("\uFFFF");
rangeScans.add(new RangeScan(reader,
new Term(field, prefix), new Term(field, limit)));
} else {
// start with initial lower case
StringBuffer lowerLimit = new StringBuffer(patternPrefix.toUpperCase());
lowerLimit.setCharAt(0, Character.toLowerCase(lowerLimit.charAt(0)));
- String prefix = FieldNames.createNamedValue(propName, lowerLimit.toString());
+ String prefix = tvf.createValue(lowerLimit.toString());
StringBuffer upperLimit = new StringBuffer(patternPrefix.toLowerCase());
upperLimit.append('\uFFFF');
- String limit = FieldNames.createNamedValue(propName, upperLimit.toString());
+ String limit = tvf.createValue(upperLimit.toString());
rangeScans.add(new RangeScan(reader,
new Term(field, prefix), new Term(field, limit)));
// second scan with upper case start
- prefix = FieldNames.createNamedValue(propName, patternPrefix.toUpperCase());
+ prefix = tvf.createValue(patternPrefix.toUpperCase());
upperLimit = new StringBuffer(patternPrefix.toLowerCase());
upperLimit.setCharAt(0, Character.toUpperCase(upperLimit.charAt(0)));
upperLimit.append('\uFFFF');
- limit = FieldNames.createNamedValue(propName, upperLimit.toString());
+ limit = tvf.createValue(upperLimit.toString());
rangeScans.add(new RangeScan(reader,
new Term(field, prefix), new Term(field, limit)));
}
@@ -299,4 +299,19 @@ class WildcardTermEnum extends FilteredT
current = it.hasNext() ? it.next() : null;
}
}
+
+ public static class TermValueFactory {
+
+ /**
+ * Creates a term value from the given string. This implementation
+ * simply returns the given string. Sub classes my apply some
+ * transformation to the string.
+ *
+ * @param s the string.
+ * @return the term value to use in the query.
+ */
+ public String createValue(String s) {
+ return s;
+ }
+ }
}
Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/GQLTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/GQLTest.java?rev=1030038&r1=1030037&r2=1030038&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/GQLTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/GQLTest.java Tue Nov 2 13:32:43 2010
@@ -297,6 +297,50 @@ public class GQLTest extends AbstractQue
checkResultSequence(rows, new Node[]{n2});
}
+ public void testName() throws RepositoryException {
+ Node file1 = addFile(testRootNode, "file1.txt", SAMPLE_CONTENT);
+ superuser.save();
+
+ String stmt = createStatement("\"quick brown\" name:file1.txt");
+ checkResultWithRetries(stmt, "jcr:content", new Node[]{file1});
+
+ stmt = createStatement("\"quick brown\" name:file?.txt");
+ checkResultWithRetries(stmt, "jcr:content", new Node[]{file1});
+
+ stmt = createStatement("\"quick brown\" name:?ile1.txt");
+ checkResultWithRetries(stmt, "jcr:content", new Node[]{file1});
+
+ stmt = createStatement("\"quick brown\" name:file1.tx?");
+ checkResultWithRetries(stmt, "jcr:content", new Node[]{file1});
+
+ stmt = createStatement("\"quick brown\" name:file1.???");
+ checkResultWithRetries(stmt, "jcr:content", new Node[]{file1});
+
+ stmt = createStatement("\"quick brown\" name:fil*xt");
+ checkResultWithRetries(stmt, "jcr:content", new Node[]{file1});
+
+ stmt = createStatement("\"quick brown\" name:*.txt");
+ checkResultWithRetries(stmt, "jcr:content", new Node[]{file1});
+
+ stmt = createStatement("\"quick brown\" name:file1.*");
+ checkResultWithRetries(stmt, "jcr:content", new Node[]{file1});
+
+ stmt = createStatement("\"quick brown\" name:*");
+ checkResultWithRetries(stmt, "jcr:content", new Node[]{file1});
+
+ stmt = createStatement("\"quick brown\" name:fIlE1.*");
+ checkResultWithRetries(stmt, "jcr:content", new Node[]{file1});
+
+ stmt = createStatement("\"quick brown\" name:file2.txt");
+ checkResultWithRetries(stmt, "jcr:content", new Node[]{});
+
+ stmt = createStatement("\"quick brown\" name:file1.t?");
+ checkResultWithRetries(stmt, "jcr:content", new Node[]{});
+
+ stmt = createStatement("\"quick brown\" name:?le1.txt");
+ checkResultWithRetries(stmt, "jcr:content", new Node[]{});
+ }
+
public void XXXtestQueryDestruction() throws RepositoryException {
char[] stmt = createStatement("title:jackrabbit \"apache software\" type:file order:+title limit:10..20").toCharArray();
for (char c = 0; c < 255; c++) {
@@ -343,11 +387,15 @@ public class GQLTest extends AbstractQue
*/
protected void checkResultWithRetries(String gql, String cpp, Node[] nodes)
throws RepositoryException {
- for (int i = 0; i < 10; i++) {
+ int retries = 10;
+ for (int i = 0; i < retries; i++) {
try {
checkResult(GQL.execute(gql, superuser, cpp), nodes);
break;
} catch (AssertionFailedError e) {
+ if (i + 1 == retries) {
+ throw e;
+ }
try {
// sleep for a second and retry
Thread.sleep(1000);
Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/FnNameQueryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/FnNameQueryTest.java?rev=1030038&r1=1030037&r2=1030038&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/FnNameQueryTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/FnNameQueryTest.java Tue Nov 2 13:32:43 2010
@@ -24,7 +24,7 @@ import javax.jcr.Node;
*/
public class FnNameQueryTest extends AbstractQueryTest {
- public void testFnName() throws RepositoryException {
+ public void testSimple() throws RepositoryException {
Node n1 = testRootNode.addNode(nodeName1);
n1.setProperty(propertyName1, 1);
Node n2 = testRootNode.addNode(nodeName2);
@@ -32,7 +32,7 @@ public class FnNameQueryTest extends Abs
Node n3 = testRootNode.addNode(nodeName3);
n3.setProperty(propertyName1, 3);
- testRootNode.save();
+ superuser.save();
String base = testPath + "/*[@" + propertyName1;
executeXPathQuery(base + " = 1 and fn:name() = '" + nodeName1 + "']",
@@ -47,11 +47,11 @@ public class FnNameQueryTest extends Abs
new Node[]{n2, n3});
}
- public void testFnNameWithSpace() throws RepositoryException {
+ public void testWithSpace() throws RepositoryException {
Node n1 = testRootNode.addNode("My Documents");
n1.setProperty(propertyName1, 1);
- testRootNode.save();
+ superuser.save();
String base = testPath + "/*[@" + propertyName1;
executeXPathQuery(base + " = 1 and fn:name() = 'My Documents']",
@@ -59,4 +59,92 @@ public class FnNameQueryTest extends Abs
executeXPathQuery(base + " = 1 and fn:name() = 'My_x0020_Documents']",
new Node[]{n1});
}
+
+ public void testLikeWithWildcard() throws RepositoryException {
+ Node n1 = testRootNode.addNode("Foo");
+ n1.setProperty(propertyName1, 1);
+
+ superuser.save();
+
+ String prefix = testPath + "/*[@" + propertyName1 + " = 1 and jcr:like(fn:name(), '";
+ String suffix = "')]";
+ executeXPathQuery(prefix + "F%" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "Fo%" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "Foo%" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "Fooo%" + suffix, new Node[]{});
+ executeXPathQuery(prefix + "%o" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "%oo" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "%Foo" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "%Foo%" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "%oo%" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "%" + suffix, new Node[]{n1});
+
+ executeXPathQuery(prefix + "F__" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "Fo_" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "F_o" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "Foo_" + suffix, new Node[]{});
+ }
+
+ public void testLikeWithWildcardAndLowerCase()
+ throws RepositoryException {
+ Node n1 = testRootNode.addNode("Foo");
+ n1.setProperty(propertyName1, 1);
+
+ superuser.save();
+
+ String prefix = testPath + "/*[@" + propertyName1 + " = 1 and jcr:like(fn:lower-case(fn:name()), '";
+ String suffix = "')]";
+
+ executeXPathQuery(prefix + "f%" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "fo%" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "foo%" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "fooo%" + suffix, new Node[]{});
+ executeXPathQuery(prefix + "%o" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "%oo" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "%foo" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "%foo%" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "%oo%" + suffix, new Node[]{n1});
+
+ executeXPathQuery(prefix + "f__" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "fo_" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "f_o" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "_oo" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "foo_" + suffix, new Node[]{});
+
+ // all non-matching
+ executeXPathQuery(prefix + "F%" + suffix, new Node[]{});
+ executeXPathQuery(prefix + "fO%" + suffix, new Node[]{});
+ executeXPathQuery(prefix + "foO%" + suffix, new Node[]{});
+ executeXPathQuery(prefix + "%O" + suffix, new Node[]{});
+ executeXPathQuery(prefix + "%Oo" + suffix, new Node[]{});
+ executeXPathQuery(prefix + "%Foo" + suffix, new Node[]{});
+ executeXPathQuery(prefix + "%FOO%" + suffix, new Node[]{});
+ executeXPathQuery(prefix + "%oO%" + suffix, new Node[]{});
+
+ executeXPathQuery(prefix + "F__" + suffix, new Node[]{});
+ executeXPathQuery(prefix + "fO_" + suffix, new Node[]{});
+ executeXPathQuery(prefix + "F_o" + suffix, new Node[]{});
+ executeXPathQuery(prefix + "_oO" + suffix, new Node[]{});
+ }
+
+ public void testLikeWithPrefix() throws RepositoryException {
+ Node n1 = testRootNode.addNode("jcr:content");
+ n1.setProperty(propertyName1, 1);
+
+ superuser.save();
+
+ String prefix = testPath + "/*[@" + propertyName1 + " = 1 and jcr:like(fn:name(), '";
+ String suffix = "')]";
+
+ executeXPathQuery(prefix + "jcr:%" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "jcr:c%" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "jcr:%ten%" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "jcr:c_nt%" + suffix, new Node[]{n1});
+ executeXPathQuery(prefix + "jcr:%nt" + suffix, new Node[]{n1});
+
+ // non-matching
+ executeXPathQuery(prefix + "invalid:content" + suffix, new Node[]{});
+ executeXPathQuery(prefix + "%:content" + suffix, new Node[]{});
+ executeXPathQuery(prefix + "%:%" + suffix, new Node[]{});
+ }
}
Modified: jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/query/GQL.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/query/GQL.java?rev=1030038&r1=1030037&r2=1030038&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/query/GQL.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/query/GQL.java Tue Nov 2 13:32:43 2010
@@ -74,6 +74,9 @@ import org.apache.jackrabbit.util.Text;
* are omitted and only one value is specified GQL will return at most this
* number of results. E.g. <code>limit:10</code> (will return the first 10
* results)</li>
+ * <li><code><b>name:</b></code> a constraint on the name of the returned nodes.
+ * The following wild cards are allowed: '*', matching any character sequence of
+ * length 0..n; '?', matching any single character.</li>
* </ul>
* <p/>
* <b>Property name</b>
@@ -150,6 +153,11 @@ public final class GQL {
private static final String LIMIT = "limit";
/**
+ * Constant for <code>name</code> keyword.
+ */
+ private static final String NAME = "name";
+
+ /**
* Constant for <code>OR</code> operator.
*/
private static final String OR = "OR";
@@ -699,6 +707,11 @@ public final class GQL {
// noise
case '*':
case '?':
+ if (property.toString().equals(NAME)) {
+ // allow wild cards in name
+ value.append(c);
+ break;
+ }
case '\'':
case '~':
case '^':
@@ -775,7 +788,13 @@ public final class GQL {
}
}
} else {
- ContainsExpression expr = new ContainsExpression(property, value);
+ Expression expr;
+ if (property.equals(NAME)) {
+ expr = new NameExpression(value);
+ } else {
+ expr = new ContainsExpression(property, value);
+ }
+
if (optional) {
Expression last = conditions.get(conditions.size() - 1);
if (last instanceof OptionalExpression) {
@@ -868,6 +887,30 @@ public final class GQL {
}
/**
+ * A name expression.
+ */
+ private class NameExpression implements Expression {
+
+ private final String value;
+
+ NameExpression(String value) {
+ String tmp = value;
+ tmp = tmp.replaceAll("'", "''");
+ tmp = tmp.replaceAll("\\*", "\\%");
+ tmp = tmp.replaceAll("\\?", "\\_");
+ tmp = tmp.toLowerCase();
+ this.value = tmp;
+ }
+
+ public void toString(StringBuffer buffer)
+ throws RepositoryException {
+ buffer.append("jcr:like(fn:lower-case(fn:name()), '");
+ buffer.append(value);
+ buffer.append("')");
+ }
+ }
+
+ /**
* A single contains expression.
*/
private final class ContainsExpression extends PropertyExpression {