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 2006/11/16 11:48:21 UTC
svn commit: r475677 [1/2] - in /jackrabbit/trunk/jackrabbit/src:
main/java/org/apache/jackrabbit/core/query/
main/java/org/apache/jackrabbit/core/query/lucene/
main/java/org/apache/jackrabbit/core/query/sql/
main/java/org/apache/jackrabbit/core/query/x...
Author: mreutegg
Date: Thu Nov 16 02:48:20 2006
New Revision: 475677
URL: http://svn.apache.org/viewvc?view=rev&rev=475677
Log:
JCR-638: Support lower-/upper-case functions
Added:
jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/PropertyFunctionQueryNode.java (with props)
jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/CaseTermQuery.java (with props)
jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/OffsetCharSequence.java (with props)
jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/RangeScan.java (with props)
jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/TransformConstants.java (with props)
jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/query/UpperLowerCaseQueryTest.java (with props)
Modified:
jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/DefaultQueryNodeVisitor.java
jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryNode.java
jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryNodeVisitor.java
jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryTreeDump.java
jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/RelationQueryNode.java
jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/FieldNames.java
jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java
jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/RangeQuery.java
jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardQuery.java
jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardTermEnum.java
jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/DefaultParserVisitor.java
jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/JCRSQLQueryBuilder.java
jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/QueryFormat.java
jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/xpath/QueryFormat.java
jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/xpath/XPathQueryBuilder.java
jackrabbit/trunk/jackrabbit/src/main/javacc/sql/JCRSQL.jjt
jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/query/TestAll.java
Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/DefaultQueryNodeVisitor.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/DefaultQueryNodeVisitor.java?view=diff&rev=475677&r1=475676&r2=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/DefaultQueryNodeVisitor.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/DefaultQueryNodeVisitor.java Thu Nov 16 02:48:20 2006
@@ -69,4 +69,8 @@
public Object visit(DerefQueryNode node, Object data) {
return data;
}
+
+ public Object visit(PropertyFunctionQueryNode node, Object data) {
+ return data;
+ }
}
Added: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/PropertyFunctionQueryNode.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/PropertyFunctionQueryNode.java?view=auto&rev=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/PropertyFunctionQueryNode.java (added)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/PropertyFunctionQueryNode.java Thu Nov 16 02:48:20 2006
@@ -0,0 +1,115 @@
+/*
+ * 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;
+
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Collections;
+
+/**
+ * <code>PropertyFunctionQueryNode</code> allows to place function calls on properties
+ * in a query. Supported function names are:
+ * <ul>
+ * <li><code>upper-case</code> as specified in <a href="http://www.w3.org/TR/xquery-operators/#func-upper-case">fn:upper-case()</a></li>
+ * <li><code>lower-case</code> as specified in <a href="http://www.w3.org/TR/xquery-operators/#func-lower-case">fn:lower-case()</a></li>
+ * </ul>
+ */
+public class PropertyFunctionQueryNode extends QueryNode {
+
+ /**
+ * Requests that property values in a {@link RelationQueryNode} are
+ * converted to upper case before they are matched with the literal.
+ */
+ public static final String UPPER_CASE = "upper-case";
+
+ /**
+ * Requests that property values in a {@link RelationQueryNode} are
+ * converted to lower case before they are matched with the literal.
+ */
+ public static final String LOWER_CASE = "lower-case";
+
+ /**
+ * The set of supported function names.
+ */
+ private static final Set SUPPORTED_FUNCTION_NAMES;
+
+ static {
+ Set tmp = new HashSet();
+ tmp.add(UPPER_CASE);
+ tmp.add(LOWER_CASE);
+ SUPPORTED_FUNCTION_NAMES = Collections.unmodifiableSet(tmp);
+ }
+
+ /**
+ * The function name.
+ */
+ private final String functionName;
+
+ /**
+ * Creates a property function query node. This query node describes a
+ * function which is applied to a property parameter of the
+ * <code>parent</code> query node.
+ *
+ * @param parent the query node where this function is applied to.
+ * @param functionName the name of the function which is applied to
+ * <code>parent</code>.
+ * @throws IllegalArgumentException if <code>functionName</code> is not a
+ * supported function.
+ */
+ public PropertyFunctionQueryNode(QueryNode parent, String functionName)
+ throws IllegalArgumentException {
+ super(parent);
+ if (!SUPPORTED_FUNCTION_NAMES.contains(functionName)) {
+ throw new IllegalArgumentException("unknown function name");
+ }
+ this.functionName = functionName;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object accept(QueryNodeVisitor visitor, Object data) {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * Returns the type of this node.
+ *
+ * @return the type of this node.
+ */
+ public int getType() {
+ return QueryNode.TYPE_PROP_FUNCTION;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals(Object obj) {
+ if (obj instanceof PropertyFunctionQueryNode) {
+ PropertyFunctionQueryNode other = (PropertyFunctionQueryNode) obj;
+ return functionName.equals(other.functionName);
+ }
+ return false;
+ }
+
+ /**
+ * @return the name of this function.
+ */
+ public String getFunctionName() {
+ return functionName;
+ }
+}
Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/PropertyFunctionQueryNode.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryNode.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryNode.java?view=diff&rev=475677&r1=475676&r2=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryNode.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryNode.java Thu Nov 16 02:48:20 2006
@@ -59,6 +59,9 @@
/** Type value for {@link DerefQueryNode} */
public static final int TYPE_DEREF = 12;
+ /** Type value for {@link PropertyFunctionQueryNode} */
+ public static final int TYPE_PROP_FUNCTION = 13;
+
/**
* References the parent of this <code>QueryNode</code>. If this is the root
* of a query tree, then <code>parent</code> is <code>null</code>.
Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryNodeVisitor.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryNodeVisitor.java?view=diff&rev=475677&r1=475676&r2=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryNodeVisitor.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryNodeVisitor.java Thu Nov 16 02:48:20 2006
@@ -44,4 +44,6 @@
Object visit(OrderQueryNode node, Object data);
Object visit(DerefQueryNode node, Object data);
+
+ Object visit(PropertyFunctionQueryNode node, Object data);
}
Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryTreeDump.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryTreeDump.java?view=diff&rev=475677&r1=475676&r2=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryTreeDump.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryTreeDump.java Thu Nov 16 02:48:20 2006
@@ -235,6 +235,7 @@
buffer.append(" Type=TIMESTAMP Value=").append(node.getDateValue());
}
buffer.append("\n");
+ traverse(node.getOperands(), buffer);
return buffer;
}
@@ -274,6 +275,15 @@
}
buffer.append("\n");
traverse(node.getOperands(), buffer);
+ return buffer;
+ }
+
+ public Object visit(PropertyFunctionQueryNode node, Object data) {
+ StringBuffer buffer = (StringBuffer) data;
+ buffer.append(PADDING, 0, indent);
+ buffer.append("+ PropertyFunctionQueryNode: ");
+ buffer.append(node.getFunctionName());
+ buffer.append("()\n");
return buffer;
}
Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/RelationQueryNode.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/RelationQueryNode.java?view=diff&rev=475677&r1=475676&r2=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/RelationQueryNode.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/RelationQueryNode.java Thu Nov 16 02:48:20 2006
@@ -23,7 +23,7 @@
/**
* Implements a query node that defines property value relation.
*/
-public class RelationQueryNode extends QueryNode implements QueryConstants {
+public class RelationQueryNode extends NAryQueryNode implements QueryConstants {
/**
* The name of the property
Added: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/CaseTermQuery.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/CaseTermQuery.java?view=auto&rev=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/CaseTermQuery.java (added)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/CaseTermQuery.java Thu Nov 16 02:48:20 2006
@@ -0,0 +1,202 @@
+/*
+ * 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 org.apache.lucene.index.Term;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.TermEnum;
+import org.apache.lucene.search.MultiTermQuery;
+import org.apache.lucene.search.FilteredTermEnum;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * <code>CaseTermQuery</code> implements a term query which convert the term
+ * from the index either to upper or lower case before it is matched.
+ */
+abstract class CaseTermQuery extends MultiTermQuery implements TransformConstants {
+
+ /**
+ * Indicates whether terms from the index should be lower-cased or
+ * upper-cased.
+ */
+ protected final int transform;
+
+ CaseTermQuery(Term term, int transform) {
+ super(term);
+ this.transform = transform;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected FilteredTermEnum getEnum(IndexReader reader) throws IOException {
+ return new CaseTermEnum(reader);
+ }
+
+ static final class Upper extends CaseTermQuery {
+
+ Upper(Term term) {
+ super(term, TRANSFORM_UPPER_CASE);
+ }
+ }
+
+ static final class Lower extends CaseTermQuery {
+
+ Lower(Term term) {
+ super(term, TRANSFORM_LOWER_CASE);
+ }
+
+ }
+
+ private final class CaseTermEnum extends FilteredTermEnum {
+
+ private final int nameLength;
+
+ private final OffsetCharSequence termText;
+
+ private final OffsetCharSequence currentTerm;
+
+ CaseTermEnum(IndexReader reader) throws IOException {
+ // gather all terms that match
+ // keep them in order and remember the doc frequency as value
+ final Map orderedTerms = new LinkedHashMap();
+
+ Term term = getTerm();
+
+ // there are always two range scanse: one with an initial
+ // lower case character and another one with an initial upper case
+ // character
+ List rangeScans = new ArrayList(2);
+ nameLength = FieldNames.getNameLength(term.text());
+ String propName = term.text().substring(0, nameLength);
+ this.termText = new OffsetCharSequence(nameLength, term.text());
+ this.currentTerm = new OffsetCharSequence(nameLength, term.text(), transform);
+
+ try {
+ // start with a term using the lower case character for the first
+ // character of the value.
+ if (term.text().length() > nameLength) {
+ // start with initial lower case
+ StringBuffer lowerLimit = new StringBuffer(propName);
+ lowerLimit.append(termText.toString().toUpperCase());
+ lowerLimit.setCharAt(nameLength, Character.toLowerCase(lowerLimit.charAt(nameLength)));
+ StringBuffer upperLimit = new StringBuffer(propName);
+ upperLimit.append(termText.toString().toLowerCase());
+ rangeScans.add(new RangeScan(reader,
+ new Term(term.field(), lowerLimit.toString()),
+ new Term(term.field(), upperLimit.toString())));
+
+ // second scan with upper case start
+ lowerLimit = new StringBuffer(propName);
+ lowerLimit.append(termText.toString().toUpperCase());
+ upperLimit = new StringBuffer(propName);
+ upperLimit.append(termText.toString().toLowerCase());
+ upperLimit.setCharAt(nameLength, Character.toUpperCase(upperLimit.charAt(nameLength)));
+ rangeScans.add(new RangeScan(reader,
+ new Term(term.field(), lowerLimit.toString()),
+ new Term(term.field(), upperLimit.toString())));
+
+ } else {
+ // use term as is
+ rangeScans.add(new RangeScan(reader, term, term));
+ }
+
+ for (Iterator it = rangeScans.iterator(); it.hasNext(); ) {
+ TermEnum terms = (TermEnum) it.next();
+ do {
+ Term t = terms.term();
+ if (t != null) {
+ currentTerm.setBase(t.text());
+ int compare = currentTerm.compareTo(termText);
+ if (compare == 0) {
+ orderedTerms.put(t, new Integer(terms.docFreq()));
+ } else if (compare < 0) {
+ // try next one
+ } else {
+ // compare > 0
+ }
+ } else {
+ break;
+ }
+ } while (terms.next());
+ }
+ } finally {
+ for (Iterator it = rangeScans.iterator(); it.hasNext(); ) {
+ TermEnum terms = (TermEnum) it.next();
+ try {
+ terms.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+
+ final Iterator it = orderedTerms.keySet().iterator();
+
+ setEnum(new TermEnum() {
+
+ private Term current;
+
+ {
+ getNext();
+ }
+
+ public boolean next() {
+ getNext();
+ return current != null;
+ }
+
+ public Term term() {
+ return current;
+ }
+
+ public int docFreq() {
+ Integer docFreq = (Integer) orderedTerms.get(current);
+ return docFreq != null ? docFreq.intValue() : 0;
+ }
+
+ public void close() {
+ // nothing to close
+ }
+
+ private void getNext() {
+ current = it.hasNext() ? (Term) it.next() : null;
+ }
+ });
+ }
+
+ protected boolean termCompare(Term term) {
+ // they all match
+ return true;
+ }
+
+ protected float difference() {
+ return 1.0f;
+ }
+
+ protected boolean endEnum() {
+ // todo correct?
+ return false;
+ }
+ }
+}
Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/CaseTermQuery.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/FieldNames.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/FieldNames.java?view=diff&rev=475677&r1=475676&r2=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/FieldNames.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/FieldNames.java Thu Nov 16 02:48:20 2006
@@ -82,4 +82,16 @@
return fieldName + '\uFFFF' + value;
}
+ /**
+ * Returns the length of the field prefix in <code>namedValue</code>. See
+ * also {@link #createNamedValue(String, String)}. If <code>namedValue</code>
+ * does not contain a name prefix, this method return 0.
+ *
+ * @param namedValue the named value as created by {@link #createNamedValue(String, String)}.
+ * @return the length of the field prefix including the separator char
+ * (\uFFFF).
+ */
+ public static int getNameLength(String namedValue) {
+ return namedValue.indexOf('\uFFFF') + 1;
+ }
}
Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java?view=diff&rev=475677&r1=475676&r2=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java Thu Nov 16 02:48:20 2006
@@ -33,6 +33,8 @@
import org.apache.jackrabbit.core.query.QueryRootNode;
import org.apache.jackrabbit.core.query.RelationQueryNode;
import org.apache.jackrabbit.core.query.TextsearchQueryNode;
+import org.apache.jackrabbit.core.query.PropertyFunctionQueryNode;
+import org.apache.jackrabbit.core.query.DefaultQueryNodeVisitor;
import org.apache.jackrabbit.core.query.lucene.fulltext.QueryParser;
import org.apache.jackrabbit.core.query.lucene.fulltext.ParseException;
import org.apache.jackrabbit.core.state.ItemStateManager;
@@ -585,6 +587,19 @@
return data;
}
+ // get property transformation
+ final int[] transform = new int[]{TransformConstants.TRANSFORM_NONE};
+ node.acceptOperands(new DefaultQueryNodeVisitor() {
+ public Object visit(PropertyFunctionQueryNode node, Object data) {
+ if (node.getFunctionName().equals(PropertyFunctionQueryNode.LOWER_CASE)) {
+ transform[0] = TransformConstants.TRANSFORM_LOWER_CASE;
+ } else if (node.getFunctionName().equals(PropertyFunctionQueryNode.UPPER_CASE)) {
+ transform[0] = TransformConstants.TRANSFORM_UPPER_CASE;
+ }
+ return data;
+ }
+ }, null);
+
String field = "";
try {
field = NameFormat.format(node.getProperty(), nsMappings);
@@ -598,8 +613,17 @@
case QueryConstants.OPERATION_EQ_GENERAL:
BooleanQuery or = new BooleanQuery();
for (int i = 0; i < stringValues.length; i++) {
- or.add(new TermQuery(new Term(FieldNames.PROPERTIES,
- FieldNames.createNamedValue(field, stringValues[i]))), false, false);
+ Term t = new Term(FieldNames.PROPERTIES,
+ FieldNames.createNamedValue(field, stringValues[i]));
+ Query q;
+ if (transform[0] == TransformConstants.TRANSFORM_UPPER_CASE) {
+ q = new CaseTermQuery.Upper(t);
+ } else if (transform[0] == TransformConstants.TRANSFORM_LOWER_CASE) {
+ q = new CaseTermQuery.Lower(t);
+ } else {
+ q = new TermQuery(t);
+ }
+ or.add(q, false, false);
}
query = or;
if (node.getOperation() == QueryConstants.OPERATION_EQ_VALUE) {
@@ -612,7 +636,7 @@
for (int i = 0; i < stringValues.length; i++) {
Term lower = new Term(FieldNames.PROPERTIES, FieldNames.createNamedValue(field, stringValues[i]));
Term upper = new Term(FieldNames.PROPERTIES, FieldNames.createNamedValue(field, "\uFFFF"));
- or.add(new RangeQuery(lower, upper, true), false, false);
+ or.add(new RangeQuery(lower, upper, true, transform[0]), false, false);
}
query = or;
if (node.getOperation() == QueryConstants.OPERATION_GE_VALUE) {
@@ -625,7 +649,7 @@
for (int i = 0; i < stringValues.length; i++) {
Term lower = new Term(FieldNames.PROPERTIES, FieldNames.createNamedValue(field, stringValues[i]));
Term upper = new Term(FieldNames.PROPERTIES, FieldNames.createNamedValue(field, "\uFFFF"));
- or.add(new RangeQuery(lower, upper, false), false, false);
+ or.add(new RangeQuery(lower, upper, false, transform[0]), false, false);
}
query = or;
if (node.getOperation() == QueryConstants.OPERATION_GT_VALUE) {
@@ -638,7 +662,7 @@
for (int i = 0; i < stringValues.length; i++) {
Term lower = new Term(FieldNames.PROPERTIES, FieldNames.createNamedValue(field, ""));
Term upper = new Term(FieldNames.PROPERTIES, FieldNames.createNamedValue(field, stringValues[i]));
- or.add(new RangeQuery(lower, upper, true), false, false);
+ or.add(new RangeQuery(lower, upper, true, transform[0]), false, false);
}
query = or;
if (node.getOperation() == QueryConstants.OPERATION_LE_VALUE) {
@@ -651,7 +675,7 @@
if (stringValues[0].equals("%")) {
query = new MatchAllQuery(field);
} else {
- query = new WildcardQuery(FieldNames.PROPERTIES, field, stringValues[0]);
+ query = new WildcardQuery(FieldNames.PROPERTIES, field, stringValues[0], transform[0]);
}
break;
case QueryConstants.OPERATION_LT_VALUE: // <
@@ -660,7 +684,7 @@
for (int i = 0; i < stringValues.length; i++) {
Term lower = new Term(FieldNames.PROPERTIES, FieldNames.createNamedValue(field, ""));
Term upper = new Term(FieldNames.PROPERTIES, FieldNames.createNamedValue(field, stringValues[i]));
- or.add(new RangeQuery(lower, upper, false), false, false);
+ or.add(new RangeQuery(lower, upper, false, transform[0]), false, false);
}
query = or;
if (node.getOperation() == QueryConstants.OPERATION_LT_VALUE) {
@@ -674,7 +698,15 @@
// exclude all nodes where 'field' has the term in question
for (int i = 0; i < stringValues.length; i++) {
Term t = new Term(FieldNames.PROPERTIES, FieldNames.createNamedValue(field, stringValues[i]));
- notQuery.add(new TermQuery(t), false, true);
+ Query q;
+ if (transform[0] == TransformConstants.TRANSFORM_UPPER_CASE) {
+ q = new CaseTermQuery.Upper(t);
+ } else if (transform[0] == TransformConstants.TRANSFORM_LOWER_CASE) {
+ q = new CaseTermQuery.Lower(t);
+ } else {
+ q = new TermQuery(t);
+ }
+ notQuery.add(q, false, true);
}
// and exclude all nodes where 'field' is multi valued
notQuery.add(new TermQuery(new Term(FieldNames.MVP, field)), false, true);
@@ -694,7 +726,15 @@
Term t = new Term(FieldNames.PROPERTIES, FieldNames.createNamedValue(field, stringValues[i]));
Query svp = new NotQuery(new TermQuery(new Term(FieldNames.MVP, field)));
BooleanQuery and = new BooleanQuery();
- and.add(new TermQuery(t), true, false);
+ Query q;
+ if (transform[0] == TransformConstants.TRANSFORM_UPPER_CASE) {
+ q = new CaseTermQuery.Upper(t);
+ } else if (transform[0] == TransformConstants.TRANSFORM_LOWER_CASE) {
+ q = new CaseTermQuery.Lower(t);
+ } else {
+ q = new TermQuery(t);
+ }
+ and.add(q, true, false);
and.add(svp, true, false);
notQuery.add(and, false, true);
}
@@ -716,6 +756,10 @@
}
public Object visit(OrderQueryNode node, Object data) {
+ return data;
+ }
+
+ public Object visit(PropertyFunctionQueryNode node, Object data) {
return data;
}
Added: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/OffsetCharSequence.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/OffsetCharSequence.java?view=auto&rev=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/OffsetCharSequence.java (added)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/OffsetCharSequence.java Thu Nov 16 02:48:20 2006
@@ -0,0 +1,146 @@
+/*
+ * 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;
+
+/**
+ * CharSequence that applies an offset to a base CharSequence. The base
+ * CharSequence can be replaced without creating a new CharSequence.
+ */
+final class OffsetCharSequence implements CharSequence, Comparable, TransformConstants {
+
+ /**
+ * Indicates how the underlying char sequence is exposed / tranformed.
+ */
+ private final int transform;
+
+ /**
+ * The offset to apply to the base CharSequence
+ */
+ private final int offset;
+
+ /**
+ * The base character sequence
+ */
+ private CharSequence base;
+
+ /**
+ * Creates a new OffsetCharSequence with an <code>offset</code>.
+ *
+ * @param offset the offset
+ * @param base the base CharSequence
+ * @param transform how the <code>base</code> char sequence is
+ * tranformed.
+ */
+ OffsetCharSequence(int offset, CharSequence base, int transform) {
+ this.offset = offset;
+ this.base = base;
+ this.transform = transform;
+ }
+
+ /**
+ * Creates a new OffsetCharSequence with an <code>offset</code>.
+ *
+ * @param offset the offset
+ * @param base the base CharSequence
+ */
+ OffsetCharSequence(int offset, CharSequence base) {
+ this(offset, base, TRANSFORM_NONE);
+ }
+
+ /**
+ * Sets a new base sequence.
+ *
+ * @param base the base character sequence
+ */
+ public void setBase(CharSequence base) {
+ this.base = base;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public int length() {
+ return base.length() - offset;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public char charAt(int index) {
+ if (transform == TRANSFORM_NONE) {
+ return base.charAt(index + offset);
+ } else if (transform == TRANSFORM_LOWER_CASE) {
+ return Character.toLowerCase(base.charAt(index + offset));
+ } else if (transform == TRANSFORM_UPPER_CASE) {
+ return Character.toUpperCase(base.charAt(index + offset));
+ }
+ // shouldn't get here. return plain character
+ return base.charAt(index + offset);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public CharSequence subSequence(int start, int end) {
+ CharSequence seq = base.subSequence(start + offset, end + offset);
+ if (transform != TRANSFORM_NONE) {
+ seq = new OffsetCharSequence(0, seq, transform);
+ }
+ return seq;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public String toString() {
+ if (transform == TRANSFORM_NONE) {
+ return base.subSequence(offset, base.length()).toString();
+ } else {
+ int len = length();
+ StringBuffer buf = new StringBuffer(len);
+ for (int i = 0; i < len; i++) {
+ buf.append(charAt(i));
+ }
+ return buf.toString();
+ }
+ }
+
+ //-----------------------------< Comparable >-------------------------------
+
+ /**
+ * Compares this char sequence to another char sequence <code>o</code>.
+ *
+ * @param o the other char sequence.
+ * @return as defined in {@link String#compareTo(Object)} but also takes
+ * {@link #transform} into account.
+ */
+ public int compareTo(Object o) {
+ OffsetCharSequence other = (OffsetCharSequence) o;
+ int len1 = length();
+ int len2 = other.length();
+ int lim = Math.min(len1, len2);
+
+ for (int i = 0; i < lim; i++) {
+ char c1 = charAt(i);
+ char c2 = other.charAt(i);
+ if (c1 != c2) {
+ return c1 - c2;
+ }
+ }
+ return len1 - len2;
+ }
+}
Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/OffsetCharSequence.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/RangeQuery.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/RangeQuery.java?view=diff&rev=475677&r1=475676&r2=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/RangeQuery.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/RangeQuery.java Thu Nov 16 02:48:20 2006
@@ -35,6 +35,9 @@
import java.util.BitSet;
import java.util.Map;
import java.util.WeakHashMap;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Iterator;
/**
* Implements a variant of the lucene class {@link org.apache.lucene.search.RangeQuery}.
@@ -42,7 +45,7 @@
* but will calculate the matching documents itself. That way a
* <code>TooManyClauses</code> can be avoided.
*/
-public class RangeQuery extends Query {
+public class RangeQuery extends Query implements TransformConstants {
/**
* Logger instance for this class.
@@ -73,14 +76,21 @@
private boolean inclusive;
/**
+ * How the term enum is transformed before it is compared to lower and upper
+ * term.
+ */
+ private final int transform;
+
+ /**
* Creates a new RangeQuery. The lower or the upper term may be
* <code>null</code>, but not both!
*
* @param lowerTerm the lower term of the interval, or <code>null</code>
* @param upperTerm the upper term of the interval, or <code>null</code>.
* @param inclusive if <code>true</code> the interval is inclusive.
+ * @param transform how term enums are transformed when read from the index.
*/
- public RangeQuery(Term lowerTerm, Term upperTerm, boolean inclusive) {
+ public RangeQuery(Term lowerTerm, Term upperTerm, boolean inclusive, int transform) {
if (lowerTerm == null && upperTerm == null) {
throw new IllegalArgumentException("At least one term must be non-null");
}
@@ -97,6 +107,7 @@
this.upperTerm = upperTerm;
this.inclusive = inclusive;
+ this.transform = transform;
}
/**
@@ -109,13 +120,19 @@
* @throws IOException if an error occurs.
*/
public Query rewrite(IndexReader reader) throws IOException {
- Query stdRangeQueryImpl
- = new org.apache.lucene.search.RangeQuery(lowerTerm, upperTerm, inclusive);
- try {
- return stdRangeQueryImpl.rewrite(reader);
- } catch (BooleanQuery.TooManyClauses e) {
- log.debug("Too many terms to enumerate, using custom RangeQuery");
- // failed, use own implementation
+ if (transform == TRANSFORM_NONE) {
+ Query stdRangeQueryImpl
+ = new org.apache.lucene.search.RangeQuery(lowerTerm, upperTerm, inclusive);
+ try {
+ return stdRangeQueryImpl.rewrite(reader);
+ } catch (BooleanQuery.TooManyClauses e) {
+ log.debug("Too many terms to enumerate, using custom RangeQuery");
+ // failed, use own implementation
+ return this;
+ }
+ } else {
+ // always use our implementation when we need to transform the
+ // term enum
return this;
}
}
@@ -283,6 +300,8 @@
key.append(upperTerm != null ? upperTerm.text() : "");
key.append('\uFFFF');
key.append(inclusive);
+ key.append('\uFFFF');
+ key.append(transform);
this.cacheKey = key.toString();
// check cache
synchronized (cache) {
@@ -352,53 +371,121 @@
return;
}
- TermEnum enumerator = reader.terms(lowerTerm);
+ String testField = getField();
- try {
- boolean checkLower = false;
- if (!inclusive) {
- // make adjustments to set to exclusive
- checkLower = true;
- }
+ boolean checkLower = false;
+ if (!inclusive || transform != TRANSFORM_NONE) {
+ // make adjustments to set to exclusive
+ checkLower = true;
+ }
- String testField = getField();
+ int propNameLength = FieldNames.getNameLength(lowerTerm.text());
+ String namePrefix = "";
+ if (propNameLength > 0) {
+ namePrefix = lowerTerm.text().substring(0, propNameLength);
+ }
+ List startTerms = new ArrayList(2);
- TermDocs docs = reader.termDocs();
+ if (transform == TRANSFORM_NONE || lowerTerm.text().length() <= propNameLength) {
+ // use lowerTerm as is
+ startTerms.add(lowerTerm);
+ } else {
+ // first enumerate terms using lower case start character
+ StringBuffer termText = new StringBuffer(propNameLength + 1);
+ termText.append(lowerTerm.text().subSequence(0, propNameLength));
+ char startCharacter = lowerTerm.text().charAt(propNameLength);
+ termText.append(Character.toLowerCase(startCharacter));
+ startTerms.add(new Term(lowerTerm.field(), termText.toString()));
+ // second enumerate terms using upper case start character
+ termText.setCharAt(termText.length() - 1, Character.toUpperCase(startCharacter));
+ startTerms.add(new Term(lowerTerm.field(), termText.toString()));
+ }
+
+ for (Iterator it = startTerms.iterator(); it.hasNext(); ) {
+ Term startTerm = (Term) it.next();
+
+ TermEnum terms = reader.terms(startTerm);
try {
- do {
- Term term = enumerator.term();
- if (term != null && term.field() == testField) {
- if (!checkLower || term.text().compareTo(lowerTerm.text()) > 0) {
- checkLower = false;
+ TermDocs docs = reader.termDocs();
+ try {
+ do {
+ Term term = terms.term();
+ if (term != null && term.field() == testField) {
+ if (checkLower) {
+ int compare = termCompare(term.text(), lowerTerm.text(), propNameLength);
+ if (compare > 0 || compare == 0 && inclusive) {
+ // do not check lower term anymore if no
+ // transformation is done on the term enum
+ checkLower = transform == TRANSFORM_NONE ? false : true;
+ } else {
+ // continue with next term
+ continue;
+ }
+ }
if (upperTerm != null) {
- int compare = upperTerm.text().compareTo(term.text());
+ int compare = termCompare(term.text(), upperTerm.text(), propNameLength);
// if beyond the upper term, or is exclusive and
- // this is equal to the upper term, break out
- if ((compare < 0) || (!inclusive && compare == 0)) {
- break;
+ // this is equal to the upper term
+ if ((compare > 0) || (!inclusive && compare == 0)) {
+ // only break out if no transformation
+ // was done on the term from the enum
+ if (transform == TRANSFORM_NONE) {
+ break;
+ } else {
+ // because of the transformation
+ // it is possible that the next
+ // term will be included again if
+ // we still enumerate on the same
+ // property name
+ if (term.text().startsWith(namePrefix)) {
+ continue;
+ } else {
+ break;
+ }
+ }
}
}
- docs.seek(enumerator);
+ docs.seek(terms);
while (docs.next()) {
hits.set(docs.doc());
}
+ } else {
+ break;
}
- } else {
- break;
- }
- } while (enumerator.next());
+ } while(terms.next());
+ } finally {
+ docs.close();
+ }
} finally {
- docs.close();
+ terms.close();
}
- } finally {
- enumerator.close();
}
+
hitsCalculated = true;
// put to cache
synchronized (resultMap) {
resultMap.put(cacheKey, hits);
}
+ }
+
+ /**
+ * Compares the <code>text</code> with the <code>other</code> String. This
+ * implementation behaves like {@link String#compareTo(Object)} but also
+ * respects the {@link RangeQuery#transform} property.
+ *
+ * @param text the text to compare to <code>other</code>. The
+ * transformation function is applied to this parameter before
+ * it is compared to <code>other</code>.
+ * @param other the other String.
+ * @param offset start comparing the two strings at <code>offset</code>.
+ * @return see {@link String#compareTo(Object)}. But also respects {@link
+ * #transform}.
+ */
+ private int termCompare(String text, String other, int offset) {
+ OffsetCharSequence seq1 = new OffsetCharSequence(offset, text, transform);
+ OffsetCharSequence seq2 = new OffsetCharSequence(offset, other);
+ return seq1.compareTo(seq2);
}
}
}
Added: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/RangeScan.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/RangeScan.java?view=auto&rev=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/RangeScan.java (added)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/RangeScan.java Thu Nov 16 02:48:20 2006
@@ -0,0 +1,60 @@
+/*
+ * 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 org.apache.lucene.index.Term;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.FilteredTermEnum;
+
+import java.io.IOException;
+
+/**
+ * <code>RangeScan</code> implements a range scan on terms.
+ */
+class RangeScan extends FilteredTermEnum {
+
+ private final Term upper;
+
+ private boolean endEnum = false;
+
+ /**
+ * Scans the index beginning at <code>lower</code> Term to <code>upper</code>.
+ * @param reader the index reader;
+ * @param lower the lower limit.
+ * @param upper the upper limit.
+ */
+ RangeScan(IndexReader reader, Term lower, Term upper) throws IOException {
+ this.upper = upper;
+ setEnum(reader.terms(lower));
+ }
+
+ protected boolean termCompare(Term term) {
+ int compare = term.compareTo(upper);
+ if (compare > 0) {
+ endEnum = true;
+ }
+ return compare <= 0;
+ }
+
+ protected float difference() {
+ return 1.0f;
+ }
+
+ protected boolean endEnum() {
+ return endEnum;
+ }
+}
Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/RangeScan.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/TransformConstants.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/TransformConstants.java?view=auto&rev=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/TransformConstants.java (added)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/TransformConstants.java Thu Nov 16 02:48:20 2006
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+/**
+ * <code>TransformConstants</code> defines constants for query processing.
+ */
+public interface TransformConstants {
+
+ /**
+ * No transformation is done on the term enum.
+ */
+ static final int TRANSFORM_NONE = 0;
+
+ /**
+ * The underlying term enum is transformed to lower case characters.
+ */
+ static final int TRANSFORM_LOWER_CASE = 1;
+
+ /**
+ * The underlying term enum is transformed to upper case characters.
+ */
+ static final int TRANSFORM_UPPER_CASE = 2;
+
+}
Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/TransformConstants.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardQuery.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardQuery.java?view=diff&rev=475677&r1=475676&r2=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardQuery.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardQuery.java Thu Nov 16 02:48:20 2006
@@ -48,7 +48,7 @@
* <li><code>_</code> : matches exactly one character</li>
* </ul>
*/
-public class WildcardQuery extends Query {
+public class WildcardQuery extends Query implements TransformConstants {
/**
* Logger instance for this class.
@@ -71,6 +71,12 @@
private final String pattern;
/**
+ * How property values are tranformed before they are matched using the
+ * provided pattern.
+ */
+ private final int transform;
+
+ /**
* Simple result cache for previously calculated hits.
* key=IndexReader value=Map{key=String:pattern,value=BitSet:hits}
*/
@@ -79,14 +85,28 @@
/**
* Creates a new <code>WildcardQuery</code>.
*
- * @param field the name of the field to search.
- * @param propName name of the property to search.
- * @param pattern the wildcard pattern.
+ * @param field the name of the field to search.
+ * @param propName name of the property to search.
+ * @param pattern the wildcard pattern.
+ * @param transform how property values are transformed before they are
+ * matched using the <code>pattern</code>.
*/
- public WildcardQuery(String field, String propName, String pattern) {
+ public WildcardQuery(String field, String propName, String pattern, int transform) {
this.field = field.intern();
this.propName = propName;
this.pattern = pattern;
+ this.transform = transform;
+ }
+
+ /**
+ * Creates a new <code>WildcardQuery</code>.
+ *
+ * @param field the name of the field to search.
+ * @param propName name of the property to search.
+ * @param pattern the wildcard pattern.
+ */
+ public WildcardQuery(String field, String propName, String pattern) {
+ this(field, propName, pattern, TRANSFORM_NONE);
}
/**
@@ -101,7 +121,7 @@
public Query rewrite(IndexReader reader) throws IOException {
Query stdWildcardQuery = new MultiTermQuery(new Term(FieldNames.PROPERTIES, pattern)) {
protected FilteredTermEnum getEnum(IndexReader reader) throws IOException {
- return new WildcardTermEnum(reader, field, propName, pattern);
+ return new WildcardTermEnum(reader, field, propName, pattern, transform);
}
};
try {
@@ -245,7 +265,7 @@
WildcardQueryScorer(Similarity similarity, IndexReader reader) {
super(similarity);
this.reader = reader;
- this.cacheKey = field + '\uFFFF' + propName + '\uFFFF' + pattern;
+ this.cacheKey = field + '\uFFFF' + propName + '\uFFFF' + transform +'\uFFFF' + pattern;
// check cache
synchronized (cache) {
Map m = (Map) cache.get(reader);
@@ -313,7 +333,7 @@
if (hitsCalculated) {
return;
}
- TermEnum terms = new WildcardTermEnum(reader, field, propName, pattern);
+ TermEnum terms = new WildcardTermEnum(reader, field, propName, pattern, transform);
try {
// use unpositioned TermDocs
TermDocs docs = reader.termDocs();
Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardTermEnum.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardTermEnum.java?view=diff&rev=475677&r1=475676&r2=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardTermEnum.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardTermEnum.java Thu Nov 16 02:48:20 2006
@@ -18,17 +18,23 @@
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermEnum;
import org.apache.lucene.search.FilteredTermEnum;
import java.io.IOException;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
+import java.util.Map;
+import java.util.LinkedHashMap;
+import java.util.Iterator;
+import java.util.ArrayList;
+import java.util.List;
/**
* Implements a wildcard term enum that optionally supports embedded property
* names in lucene term texts.
*/
-class WildcardTermEnum extends FilteredTermEnum {
+class WildcardTermEnum extends FilteredTermEnum implements TransformConstants {
/**
* The pattern matcher.
@@ -56,20 +62,35 @@
private final OffsetCharSequence input;
/**
+ * How terms from the index are transformed.
+ */
+ private final int transform;
+
+ /**
* Creates a new <code>WildcardTermEnum</code>.
*
- * @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 pattern the pattern to match the values.
- * @throws IOException if an error occurs while reading from the index.
+ * @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 pattern the pattern to match the values.
+ * @param transform the transformation that should be applied to the term
+ * enum from the index reader.
+ * @throws IOException if an error occurs while reading from
+ * the index.
+ * @throws IllegalArgumentException if <code>transform</code> is not a valid
+ * value.
*/
public WildcardTermEnum(IndexReader reader,
String field,
String propName,
- String pattern) throws IOException {
+ String pattern,
+ int transform) throws IOException {
+ if (transform < TRANSFORM_NONE || transform > TRANSFORM_UPPER_CASE) {
+ throw new IllegalArgumentException("invalid transform parameter");
+ }
this.field = field;
+ this.transform = transform;
int idx = 0;
while (idx < pattern.length()
@@ -84,22 +105,31 @@
}
// initialize with prefix as dummy value
- input = new OffsetCharSequence(prefix.length(), prefix);
+ input = new OffsetCharSequence(prefix.length(), prefix, transform);
this.pattern = createRegexp(pattern.substring(idx)).matcher(input);
- setEnum(reader.terms(new Term(field, prefix)));
+ if (transform == TRANSFORM_NONE) {
+ setEnum(reader.terms(new Term(field, prefix)));
+ } else {
+ setEnum(new LowerUpperCaseTermEnum(reader, field, propName, pattern, transform));
+ }
}
/**
* @inheritDoc
*/
protected boolean termCompare(Term term) {
- if (term.field() == field && term.text().startsWith(prefix)) {
- input.setBase(term.text());
- return pattern.reset().matches();
+ if (transform == TRANSFORM_NONE) {
+ if (term.field() == field && term.text().startsWith(prefix)) {
+ input.setBase(term.text());
+ return pattern.reset().matches();
+ }
+ endEnum = true;
+ return false;
+ } else {
+ // pre filtered, no need to check
+ return true;
}
- endEnum = true;
- return false;
}
/**
@@ -169,67 +199,143 @@
}
/**
- * CharSequence that applies an offset to a base CharSequence. The base
- * CharSequence can be replaced without creating a new CharSequence.
+ * Implements a term enum which respects the transformation flag and
+ * matches a pattern on the enumerated terms.
*/
- private static final class OffsetCharSequence implements CharSequence {
+ private class LowerUpperCaseTermEnum extends TermEnum {
/**
- * The offset to apply to the base CharSequence
+ * The matching terms
*/
- private final int offset;
+ private final Map orderedTerms = new LinkedHashMap();
/**
- * The base character sequence
+ * Iterator over all matching terms
*/
- private CharSequence base;
+ private final Iterator it;
+
+ public LowerUpperCaseTermEnum(IndexReader reader,
+ String field,
+ String propName,
+ String pattern,
+ int transform) throws IOException {
+ if (transform != TRANSFORM_LOWER_CASE && transform != TRANSFORM_UPPER_CASE) {
+ throw new IllegalArgumentException("transform");
+ }
+
+ // create range scans
+ List rangeScans = new ArrayList(2);
+ try {
+ int idx = 0;
+ while (idx < pattern.length()
+ && Character.isLetterOrDigit(pattern.charAt(idx))) {
+ idx++;
+ }
+ 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");
+ 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());
+
+ StringBuffer upperLimit = new StringBuffer(patternPrefix.toLowerCase());
+ upperLimit.append('\uFFFF');
+ String limit = FieldNames.createNamedValue(propName, 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());
+ upperLimit = new StringBuffer(patternPrefix.toLowerCase());
+ upperLimit.setCharAt(0, Character.toUpperCase(upperLimit.charAt(0)));
+ upperLimit.append('\uFFFF');
+ limit = FieldNames.createNamedValue(propName, upperLimit.toString());
+ rangeScans.add(new RangeScan(reader,
+ new Term(field, prefix), new Term(field, limit)));
+ }
+
+ String prefix = FieldNames.createNamedValue(propName, patternPrefix);
+ // initialize with prefix as dummy value
+ OffsetCharSequence input = new OffsetCharSequence(prefix.length(), prefix, transform);
+ Matcher matcher = createRegexp(pattern.substring(idx)).matcher(input);
+
+ // do range scans with patter matcher
+ for (Iterator it = rangeScans.iterator(); it.hasNext(); ) {
+ RangeScan scan = (RangeScan) it.next();
+ do {
+ Term t = scan.term();
+ if (t != null) {
+ input.setBase(t.text());
+ if (matcher.reset().matches()) {
+ orderedTerms.put(t, new Integer(scan.docFreq()));
+ }
+ }
+ } while (scan.next());
+ }
+
+ } finally {
+ // close range scans
+ for (Iterator it = rangeScans.iterator(); it.hasNext(); ) {
+ RangeScan scan = (RangeScan) it.next();
+ try {
+ scan.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+
+ it = orderedTerms.keySet().iterator();
+ getNext();
+ }
/**
- * Creates a new OffsetCharSequence with an <code>offset</code>.
- *
- * @param offset the offset
- * @param base the base CharSequence
+ * The current term in this enum.
*/
- OffsetCharSequence(int offset, CharSequence base) {
- this.offset = offset;
- this.base = base;
- }
+ private Term current;
/**
- * Sets a new base sequence.
- *
- * @param base the base character sequence
+ * {@inheritDoc}
*/
- public void setBase(CharSequence base) {
- this.base = base;
+ public boolean next() {
+ getNext();
+ return current != null;
}
/**
- * @inheritDoc
+ * {@inheritDoc}
*/
- public int length() {
- return base.length() - offset;
+ public Term term() {
+ return current;
}
/**
- * @inheritDoc
+ * {@inheritDoc}
*/
- public char charAt(int index) {
- return base.charAt(index + offset);
+ public int docFreq() {
+ Integer docFreq = (Integer) orderedTerms.get(current);
+ return docFreq != null ? docFreq.intValue() : 0;
}
/**
- * @inheritDoc
+ * {@inheritDoc}
*/
- public CharSequence subSequence(int start, int end) {
- return base.subSequence(start + offset, end + offset);
+ public void close() {
+ // nothing to do here
}
/**
- * @inheritDoc
+ * Sets the current field to the next term in this enum or to
+ * <code>null</code> if there is no next.
*/
- public String toString() {
- return base.subSequence(offset, base.length()).toString();
+ private void getNext() {
+ current = it.hasNext() ? (Term) it.next() : null;
}
}
}
Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/DefaultParserVisitor.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/DefaultParserVisitor.java?view=diff&rev=475677&r1=475676&r2=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/DefaultParserVisitor.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/DefaultParserVisitor.java Thu Nov 16 02:48:20 2006
@@ -89,4 +89,12 @@
public Object visit(ASTDescendingOrderSpec node, Object data) {
return data;
}
+
+ public Object visit(ASTLowerFunction node, Object data) {
+ return data;
+ }
+
+ public Object visit(ASTUpperFunction node, Object data) {
+ return data;
+ }
}
Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/JCRSQLQueryBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/JCRSQLQueryBuilder.java?view=diff&rev=475677&r1=475676&r2=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/JCRSQLQueryBuilder.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/JCRSQLQueryBuilder.java Thu Nov 16 02:48:20 2006
@@ -29,6 +29,7 @@
import org.apache.jackrabbit.core.query.QueryRootNode;
import org.apache.jackrabbit.core.query.RelationQueryNode;
import org.apache.jackrabbit.core.query.TextsearchQueryNode;
+import org.apache.jackrabbit.core.query.PropertyFunctionQueryNode;
import org.apache.jackrabbit.name.IllegalNameException;
import org.apache.jackrabbit.name.NamespaceResolver;
import org.apache.jackrabbit.name.QName;
@@ -295,6 +296,30 @@
value[0] = node;
return data;
}
+
+ public Object visit(ASTLowerFunction node, Object data) {
+ getIdentifier(node);
+ return data;
+ }
+
+ public Object visit(ASTUpperFunction node, Object data) {
+ getIdentifier(node);
+ return data;
+ }
+
+ private void getIdentifier(SimpleNode node) {
+ if (node.jjtGetNumChildren() > 0) {
+ Node n = node.jjtGetChild(0);
+ if (n instanceof ASTIdentifier) {
+ ASTIdentifier identifier = (ASTIdentifier) n;
+ if (tmp[0] == null) {
+ tmp[0] = identifier.getName();
+ } else if (tmp[1] == null) {
+ tmp[1] = identifier.getName();
+ }
+ }
+ }
+ }
}, data);
QName identifier = tmp[0];
@@ -312,9 +337,11 @@
AndQueryNode between = new AndQueryNode(parent);
RelationQueryNode rel = createRelationQueryNode(between,
identifier, QueryConstants.OPERATION_GE_GENERAL, (ASTLiteral) node.children[1]);
+ node.childrenAccept(this, rel);
between.addOperand(rel);
rel = createRelationQueryNode(between,
identifier, QueryConstants.OPERATION_LE_GENERAL, (ASTLiteral) node.children[2]);
+ node.childrenAccept(this, rel);
between.addOperand(rel);
predicateNode = between;
} else if (type == QueryConstants.OPERATION_GE_GENERAL
@@ -325,6 +352,7 @@
|| type == QueryConstants.OPERATION_EQ_GENERAL) {
predicateNode = createRelationQueryNode(parent,
identifier, type, value[0]);
+ node.childrenAccept(this, predicateNode);
} else if (type == QueryConstants.OPERATION_LIKE) {
ASTLiteral pattern = value[0];
if (node.getEscapeString() != null) {
@@ -342,11 +370,13 @@
}
predicateNode = createRelationQueryNode(parent,
identifier, type, pattern);
+ node.childrenAccept(this, predicateNode);
} else if (type == QueryConstants.OPERATION_IN) {
OrQueryNode in = new OrQueryNode(parent);
for (int i = 1; i < node.children.length; i++) {
RelationQueryNode rel = createRelationQueryNode(in,
identifier, QueryConstants.OPERATION_EQ_VALUE, (ASTLiteral) node.children[i]);
+ node.childrenAccept(this, rel);
in.addOperand(rel);
}
predicateNode = in;
@@ -412,12 +442,12 @@
}
public Object visit(ASTLiteral node, Object data) {
- // do nothing, should never be called actually
+ // do nothing
return data;
}
public Object visit(ASTIdentifier node, Object data) {
- // do nothing, should never be called actually
+ // do nothing
return data;
}
@@ -465,6 +495,26 @@
public Object visit(ASTContainsExpression node, Object data) {
NAryQueryNode parent = (NAryQueryNode) data;
parent.addOperand(new TextsearchQueryNode(parent, node.getQuery(), node.getPropertyName()));
+ return parent;
+ }
+
+ public Object visit(ASTLowerFunction node, Object data) {
+ RelationQueryNode parent = (RelationQueryNode) data;
+ if (parent.getValueType() != QueryConstants.TYPE_STRING) {
+ String msg = "LOWER() function is only supported for String literal";
+ throw new IllegalArgumentException(msg);
+ }
+ parent.addOperand(new PropertyFunctionQueryNode(parent, PropertyFunctionQueryNode.LOWER_CASE));
+ return parent;
+ }
+
+ public Object visit(ASTUpperFunction node, Object data) {
+ RelationQueryNode parent = (RelationQueryNode) data;
+ if (parent.getValueType() != QueryConstants.TYPE_STRING) {
+ String msg = "UPPER() function is only supported for String literal";
+ throw new IllegalArgumentException(msg);
+ }
+ parent.addOperand(new PropertyFunctionQueryNode(parent, PropertyFunctionQueryNode.UPPER_CASE));
return parent;
}
Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/QueryFormat.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/QueryFormat.java?view=diff&rev=475677&r1=475676&r2=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/QueryFormat.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/QueryFormat.java Thu Nov 16 02:48:20 2006
@@ -31,6 +31,7 @@
import org.apache.jackrabbit.core.query.QueryRootNode;
import org.apache.jackrabbit.core.query.RelationQueryNode;
import org.apache.jackrabbit.core.query.TextsearchQueryNode;
+import org.apache.jackrabbit.core.query.PropertyFunctionQueryNode;
import org.apache.jackrabbit.name.NamespaceResolver;
import org.apache.jackrabbit.name.NoPrefixDeclaredException;
import org.apache.jackrabbit.name.QName;
@@ -382,39 +383,36 @@
public Object visit(RelationQueryNode node, Object data) {
StringBuffer sb = (StringBuffer) data;
try {
+ StringBuffer propName = new StringBuffer();
+ appendName(node.getProperty(), resolver, propName);
+ // surround name with property function
+ node.acceptOperands(this, propName);
+
+ sb.append(propName);
if (node.getOperation() == OPERATION_EQ_VALUE || node.getOperation() == OPERATION_EQ_GENERAL) {
- appendName(node.getProperty(), resolver, sb);
sb.append(" = ");
appendValue(node, sb);
} else if (node.getOperation() == OPERATION_GE_VALUE || node.getOperation() == OPERATION_GE_GENERAL) {
- appendName(node.getProperty(), resolver, sb);
sb.append(" >= ");
appendValue(node, sb);
} else if (node.getOperation() == OPERATION_GT_VALUE || node.getOperation() == OPERATION_GT_GENERAL) {
- appendName(node.getProperty(), resolver, sb);
sb.append(" > ");
appendValue(node, sb);
} else if (node.getOperation() == OPERATION_LE_VALUE || node.getOperation() == OPERATION_LE_GENERAL) {
- appendName(node.getProperty(), resolver, sb);
sb.append(" <= ");
appendValue(node, sb);
} else if (node.getOperation() == OPERATION_LIKE) {
- appendName(node.getProperty(), resolver, sb);
sb.append(" LIKE ");
appendValue(node, sb);
} else if (node.getOperation() == OPERATION_LT_VALUE || node.getOperation() == OPERATION_LT_GENERAL) {
- appendName(node.getProperty(), resolver, sb);
sb.append(" < ");
appendValue(node, sb);
} else if (node.getOperation() == OPERATION_NE_VALUE || node.getOperation() == OPERATION_NE_GENERAL) {
- appendName(node.getProperty(), resolver, sb);
sb.append(" <> ");
appendValue(node, sb);
} else if (node.getOperation() == OPERATION_NULL) {
- appendName(node.getProperty(), resolver, sb);
sb.append(" IS NULL");
} else if (node.getOperation() == OPERATION_NOT_NULL) {
- appendName(node.getProperty(), resolver, sb);
sb.append(" IS NOT NULL");
} else {
exceptions.add(new InvalidQueryException("Invalid operation: " + node.getOperation()));
@@ -449,6 +447,19 @@
}
} else {
sb.append(" SCORE");
+ }
+ return sb;
+ }
+
+ public Object visit(PropertyFunctionQueryNode node, Object data) {
+ StringBuffer sb = (StringBuffer) data;
+ String functionName = node.getFunctionName();
+ if (functionName.equals(PropertyFunctionQueryNode.LOWER_CASE)) {
+ sb.insert(0, "LOWER(").append(")");
+ } else if (functionName.equals(PropertyFunctionQueryNode.UPPER_CASE)) {
+ sb.insert(0, "UPPER(").append(")");
+ } else {
+ exceptions.add(new InvalidQueryException("Unsupported function: " + functionName));
}
return sb;
}
Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/xpath/QueryFormat.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/xpath/QueryFormat.java?view=diff&rev=475677&r1=475676&r2=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/xpath/QueryFormat.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/xpath/QueryFormat.java Thu Nov 16 02:48:20 2006
@@ -31,6 +31,7 @@
import org.apache.jackrabbit.core.query.QueryRootNode;
import org.apache.jackrabbit.core.query.RelationQueryNode;
import org.apache.jackrabbit.core.query.TextsearchQueryNode;
+import org.apache.jackrabbit.core.query.PropertyFunctionQueryNode;
import org.apache.jackrabbit.name.NamespaceResolver;
import org.apache.jackrabbit.name.NoPrefixDeclaredException;
import org.apache.jackrabbit.name.QName;
@@ -294,14 +295,18 @@
StringBuffer sb = (StringBuffer) data;
try {
- String propName = "@";
+ StringBuffer propName = new StringBuffer();
// only encode if not position function
if (node.getProperty().equals(XPathQueryBuilder.FN_POSITION_FULL)) {
- propName += NameFormat.format(node.getProperty(), resolver);
+ NameFormat.format(node.getProperty(), resolver, propName);
} else {
- propName += NameFormat.format(ISO9075.encode(node.getProperty()), resolver);
+ propName.append("@");
+ NameFormat.format(ISO9075.encode(node.getProperty()), resolver, propName);
}
+ // surround name with property function
+ node.acceptOperands(this, propName);
+
if (node.getOperation() == OPERATION_EQ_VALUE) {
sb.append(propName).append(" eq ");
appendValue(node, sb);
@@ -376,6 +381,25 @@
exceptions.add(e);
}
return data;
+ }
+
+ public Object visit(PropertyFunctionQueryNode node, Object data) {
+ StringBuffer sb = (StringBuffer) data;
+ String functionName = node.getFunctionName();
+ try {
+ if (functionName.equals(PropertyFunctionQueryNode.LOWER_CASE)) {
+ sb.insert(0, NameFormat.format(XPathQueryBuilder.FN_LOWER_CASE, resolver) + "(");
+ sb.append(")");
+ } else if (functionName.equals(PropertyFunctionQueryNode.UPPER_CASE)) {
+ sb.insert(0, NameFormat.format(XPathQueryBuilder.FN_UPPER_CASE, resolver) + "(");
+ sb.append(")");
+ } else {
+ exceptions.add(new InvalidQueryException("Unsupported function: " + functionName));
+ }
+ } catch (NoPrefixDeclaredException e) {
+ exceptions.add(e);
+ }
+ return sb;
}
//----------------------------< internal >----------------------------------
Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/xpath/XPathQueryBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/xpath/XPathQueryBuilder.java?view=diff&rev=475677&r1=475676&r2=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/xpath/XPathQueryBuilder.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/xpath/XPathQueryBuilder.java Thu Nov 16 02:48:20 2006
@@ -30,6 +30,8 @@
import org.apache.jackrabbit.core.query.QueryRootNode;
import org.apache.jackrabbit.core.query.RelationQueryNode;
import org.apache.jackrabbit.core.query.TextsearchQueryNode;
+import org.apache.jackrabbit.core.query.PropertyFunctionQueryNode;
+import org.apache.jackrabbit.core.query.DefaultQueryNodeVisitor;
import org.apache.jackrabbit.name.IllegalNameException;
import org.apache.jackrabbit.name.NamespaceResolver;
import org.apache.jackrabbit.name.NoPrefixDeclaredException;
@@ -53,9 +55,24 @@
public class XPathQueryBuilder implements XPathVisitor, XPathTreeConstants {
/**
+ * Namespace uri for xpath functions. See also class SearchManager
+ */
+ static final String NS_FN_URI = "http://www.w3.org/2004/10/xpath-functions";
+
+ /**
* QName for 'fn:not'
*/
- static final QName FN_NOT = new QName("http://www.w3.org/2004/10/xpath-functions", "not");
+ static final QName FN_NOT = new QName(NS_FN_URI, "not");
+
+ /**
+ * QName for 'fn:lower-case'
+ */
+ static final QName FN_LOWER_CASE = new QName(NS_FN_URI, "lower-case");
+
+ /**
+ * QName for 'fn:upper-case'
+ */
+ static final QName FN_UPPER_CASE = new QName(NS_FN_URI, "upper-case");
/**
* QName for 'not' as defined in XPath 1.0 (no prefix)
@@ -593,11 +610,25 @@
exceptions.add(new InvalidQueryException("Unsupported ComparisonExpr type:" + node.getValue()));
}
- RelationQueryNode rqn = new RelationQueryNode(queryNode, type);
+ final RelationQueryNode rqn = new RelationQueryNode(queryNode, type);
// traverse
node.childrenAccept(this, rqn);
+ // check if string transformation is valid
+ rqn.acceptOperands(new DefaultQueryNodeVisitor() {
+ public Object visit(PropertyFunctionQueryNode node, Object data) {
+ String functionName = node.getFunctionName();
+ if ((functionName.equals(PropertyFunctionQueryNode.LOWER_CASE)
+ || functionName.equals(PropertyFunctionQueryNode.UPPER_CASE))
+ && rqn.getValueType() != QueryConstants.TYPE_STRING) {
+ String msg = "Upper and lower case function are only supported with String literals";
+ exceptions.add(new InvalidQueryException(msg));
+ }
+ return data;
+ }
+ }, null);
+
queryNode.addOperand(rqn);
}
@@ -850,6 +881,32 @@
createOrderSpec(node, (OrderQueryNode) queryNode);
} else {
exceptions.add(new InvalidQueryException("Unsupported location for jcr:score()"));
+ }
+ } else if (NameFormat.format(FN_LOWER_CASE, resolver).equals(fName)) {
+ if (node.jjtGetNumChildren() == 2) {
+ if (queryNode.getType() == QueryNode.TYPE_RELATION) {
+ RelationQueryNode relNode = (RelationQueryNode) queryNode;
+ relNode.addOperand(new PropertyFunctionQueryNode(relNode, PropertyFunctionQueryNode.LOWER_CASE));
+ // get property name
+ node.jjtGetChild(1).jjtAccept(this, relNode);
+ } else {
+ exceptions.add(new InvalidQueryException("Unsupported location for fn:lower-case()"));
+ }
+ } else {
+ exceptions.add(new InvalidQueryException("Wrong number of argument for fn:lower-case()"));
+ }
+ } else if (NameFormat.format(FN_UPPER_CASE, resolver).equals(fName)) {
+ if (node.jjtGetNumChildren() == 2) {
+ if (queryNode.getType() == QueryNode.TYPE_RELATION) {
+ RelationQueryNode relNode = (RelationQueryNode) queryNode;
+ relNode.addOperand(new PropertyFunctionQueryNode(relNode, PropertyFunctionQueryNode.UPPER_CASE));
+ // get property name
+ node.jjtGetChild(1).jjtAccept(this, relNode);
+ } else {
+ exceptions.add(new InvalidQueryException("Unsupported location for fn:upper-case()"));
+ }
+ } else {
+ exceptions.add(new InvalidQueryException("Unsupported location for fn:upper-case()"));
}
} else {
exceptions.add(new InvalidQueryException("Unsupported function: " + fName));
Modified: jackrabbit/trunk/jackrabbit/src/main/javacc/sql/JCRSQL.jjt
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/javacc/sql/JCRSQL.jjt?view=diff&rev=475677&r1=475676&r2=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/javacc/sql/JCRSQL.jjt (original)
+++ jackrabbit/trunk/jackrabbit/src/main/javacc/sql/JCRSQL.jjt Thu Nov 16 02:48:20 2006
@@ -100,7 +100,9 @@
| < LIKE: "LIKE" >
| < NULL: "NULL" >
| < FROM: "FROM" >
+| < LOWER: "LOWER" >
| < ORDER: "ORDER" >
+| < UPPER: "UPPER" >
| < WHERE: "WHERE" >
| < ESCAPE: "ESCAPE" >
| < SELECT: "SELECT" >
@@ -304,7 +306,15 @@
{
(
(
- identifier = Identifier() { jjtThis.setIdentifier(identifier); } ( <PERIOD> identifier = Identifier() { Node n = jjtree.popNode(); jjtree.popNode(); jjtree.pushNode(n); jjtThis.setIdentifier(identifier); } )?
+ (
+ (
+ identifier = Identifier() { jjtThis.setIdentifier(identifier); } ( <PERIOD> identifier = Identifier() { Node n = jjtree.popNode(); jjtree.popNode(); jjtree.pushNode(n); jjtThis.setIdentifier(identifier); } )?
+ )
+ |
+ (
+ identifier = PropertyFunction() { jjtThis.setIdentifier(identifier); }
+ )
+ )
(
(
operationType = ComparisonOperation() { jjtThis.setOperationType(operationType); }
@@ -339,7 +349,10 @@
(
Literal() (<NOT> { jjtThis.setNegate(true); })? <IN>
(
- identifier = Identifier()
+ (
+ identifier = Identifier()
+ | identifier = PropertyFunction()
+ )
{
jjtThis.setIdentifier(identifier);
jjtThis.setOperationType(jjtThis.isNegate() ? QueryConstants.OPERATION_NE_GENERAL : QueryConstants.OPERATION_EQ_GENERAL);
@@ -349,6 +362,45 @@
)
}
+QName PropertyFunction() #void :
+{
+ QName identifier;
+}
+{
+ (
+ identifier = LowerFunction()
+ | identifier = UpperFunction()
+ )
+ {
+ return identifier;
+ }
+}
+
+QName LowerFunction() :
+{
+ QName identifier;
+}
+{
+ (
+ <LOWER> "(" identifier = Identifier() ")"
+ )
+ {
+ return identifier;
+ }
+}
+
+QName UpperFunction() :
+{
+ QName identifier;
+}
+{
+ (
+ <UPPER> "(" identifier = Identifier() ")"
+ )
+ {
+ return identifier;
+ }
+}
int ComparisonOperation() #void :
{
Modified: jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/query/TestAll.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/query/TestAll.java?view=diff&rev=475677&r1=475676&r2=475677
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/query/TestAll.java (original)
+++ jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/query/TestAll.java Thu Nov 16 02:48:20 2006
@@ -45,6 +45,7 @@
suite.addTestSuite(MixinTest.class);
suite.addTestSuite(DerefTest.class);
suite.addTestSuite(VersionStoreQueryTest.class);
+ suite.addTestSuite(UpperLowerCaseQueryTest.class);
// exclude long running tests per default
//suite.addTestSuite(MassiveRangeTest.class);