Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LengthImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\npackage org.apache.jackrabbit.oak.query.ast;\n\nimport javax.jcr.PropertyType;\n\nimport org.apache.jackrabbit.oak.api.PropertyState;\nimport org.apache.jackrabbit.oak.query.index.FilterImpl;\nimport org.apache.jackrabbit.oak.spi.query.PropertyValue;\nimport org.apache.jackrabbit.oak.spi.query.PropertyValues;\n\n/**\n * The function \"length(..)\".\n */\npublic class LengthImpl extends DynamicOperandImpl {\n\n private final PropertyValueImpl propertyValue;\n\n public LengthImpl(PropertyValueImpl propertyValue) {\n this.propertyValue = propertyValue;\n }\n\n public PropertyValueImpl getPropertyValue() {\n return propertyValue;\n }\n\n @Override\n boolean accept(AstVisitor v) {\n return v.visit(this);\n }\n\n @Override\n public String toString() {\n return \"length(\" + propertyValue + ')';\n }\n\n @Override\n public PropertyValue currentProperty() {\n PropertyState p = propertyValue.currentProperty();\n if (p == null) {\n return null;\n }\n if (!p.isArray()) {\n long length = p.size();\n return PropertyValues.newLong(length);\n }\n // TODO what is the expected result for LENGTH(multiValueProperty)?\n throw new IllegalArgumentException(\"LENGTH(x) on multi-valued property is not supported\");\n }\n\n @Override\n public void restrict(FilterImpl f, Operator operator, PropertyValue v) {\n if (v != null) {\n switch (v.getType().tag()) {\n case PropertyType.LONG:\n case PropertyType.DECIMAL:\n case PropertyType.DOUBLE:\n // ok - comparison with a number\n break;\n case PropertyType.BINARY:\n case PropertyType.STRING:\n case PropertyType.DATE:\n // ok - compare with a string literal\n break;\n default:\n throw new IllegalArgumentException(\n \"Can not compare the length with a constant of type \"\n + PropertyType.nameFromValue(v.getType().tag()) +\n \" and value \" + v.toString());\n }\n }\n // LENGTH(x) implies x is not null\n propertyValue.restrict(f, Operator.NOT_EQUAL, null);\n }\n\n @Override\n public boolean canRestrictSelector(SelectorImpl s) {\n return propertyValue.canRestrictSelector(s);\n }\n\n}\n =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LengthImpl.java (revision c945219e1b9261b33382296da9d49bed60accebb) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LengthImpl.java (revision ) @@ -20,7 +20,6 @@ import javax.jcr.PropertyType; -import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.query.index.FilterImpl; import org.apache.jackrabbit.oak.spi.query.PropertyValue; import org.apache.jackrabbit.oak.spi.query.PropertyValues; @@ -52,7 +51,7 @@ @Override public PropertyValue currentProperty() { - PropertyState p = propertyValue.currentProperty(); + PropertyValue p = propertyValue.currentProperty(); if (p == null) { return null; } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/PropertyValue.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\npackage org.apache.jackrabbit.oak.spi.query;\n\nimport java.util.Calendar;\nimport java.util.Iterator;\n\nimport javax.jcr.PropertyType;\n\nimport org.apache.jackrabbit.oak.api.PropertyState;\nimport org.apache.jackrabbit.oak.api.Type;\nimport org.apache.jackrabbit.util.ISO8601;\n\npublic class PropertyValue implements PropertyState, Comparable {\n\n private final PropertyState ps;\n\n protected PropertyValue(PropertyState ps) {\n this.ps = ps;\n }\n\n public String getName() {\n return ps.getName();\n }\n\n public boolean isArray() {\n return ps.isArray();\n }\n\n public Type getType() {\n return ps.getType();\n }\n\n public T getValue(Type type) {\n return ps.getValue(type);\n }\n\n public T getValue(Type type, int index) {\n return ps.getValue(type, index);\n }\n\n public long size() {\n return ps.size();\n }\n\n public long size(int index) {\n return ps.size(index);\n }\n\n public int count() {\n return ps.count();\n }\n\n public PropertyState unwrap() {\n return ps;\n }\n\n public int compareTo(PropertyValue p2) {\n if (getType().tag() != p2.getType().tag()) {\n return Integer.signum(getType().tag() - p2.getType().tag());\n }\n switch (getType().tag()) {\n case PropertyType.BINARY:\n return compare(getValue(Type.BINARIES), p2.getValue(Type.BINARIES));\n case PropertyType.DOUBLE:\n return compare(getValue(Type.DOUBLES), p2.getValue(Type.DOUBLES));\n case PropertyType.DATE:\n return compareAsDate(getValue(Type.STRINGS),\n p2.getValue(Type.STRINGS));\n default:\n return compare(getValue(Type.STRINGS), p2.getValue(Type.STRINGS));\n }\n }\n\n private static > int compare(Iterable p1,\n Iterable p2) {\n Iterator i1 = p1.iterator();\n Iterator i2 = p2.iterator();\n while (i1.hasNext() || i2.hasNext()) {\n if (!i1.hasNext()) {\n return 1;\n }\n if (!i2.hasNext()) {\n return -1;\n }\n int compare = i1.next().compareTo(i2.next());\n if (compare != 0) {\n return compare;\n }\n }\n return 0;\n }\n\n private static int compareAsDate(Iterable p1, Iterable p2) {\n Iterator i1 = p1.iterator();\n Iterator i2 = p2.iterator();\n while (i1.hasNext() || i2.hasNext()) {\n if (!i1.hasNext()) {\n return 1;\n }\n if (!i2.hasNext()) {\n return -1;\n }\n String v1 = i1.next();\n String v2 = i2.next();\n\n Calendar c1 = ISO8601.parse(v1);\n Calendar c2 = ISO8601.parse(v2);\n int compare = -1;\n if (c1 != null && c2 != null) {\n compare = c1.compareTo(c2);\n } else {\n compare = v1.compareTo(v2);\n }\n if (compare != 0) {\n return compare;\n }\n }\n return 0;\n }\n\n // --------------------------------------------------------------< Object >\n\n private String getInternalString() {\n StringBuilder sb = new StringBuilder();\n Iterator iterator = getValue(Type.STRINGS).iterator();\n while (iterator.hasNext()) {\n sb.append(iterator.next());\n if (iterator.hasNext()) {\n sb.append(\",\");\n }\n }\n return sb.toString();\n }\n\n @Override\n public int hashCode() {\n return getType().tag() ^ getInternalString().hashCode();\n }\n\n @Override\n public boolean equals(Object o) {\n if (o == this) {\n return true;\n } else if (o instanceof PropertyValue) {\n return compareTo((PropertyValue) o) == 0;\n } else {\n return false;\n }\n }\n\n @Override\n public String toString() {\n return getInternalString();\n }\n\n}\n =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/PropertyValue.java (revision c945219e1b9261b33382296da9d49bed60accebb) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/PropertyValue.java (revision ) @@ -27,16 +27,12 @@ import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.util.ISO8601; -public class PropertyValue implements PropertyState, Comparable { +public class PropertyValue implements Comparable { private final PropertyState ps; protected PropertyValue(PropertyState ps) { this.ps = ps; - } - - public String getName() { - return ps.getName(); } public boolean isArray() { Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\npackage org.apache.jackrabbit.oak.query.ast;\n\nimport static org.apache.jackrabbit.oak.api.Type.STRING;\nimport static org.apache.jackrabbit.oak.api.Type.STRINGS;\n\nimport java.text.ParseException;\nimport java.util.ArrayList;\n\nimport org.apache.jackrabbit.oak.api.PropertyState;\nimport org.apache.jackrabbit.oak.api.Tree;\nimport org.apache.jackrabbit.oak.api.Type;\nimport org.apache.jackrabbit.oak.query.ast.ComparisonImpl.LikePattern;\nimport org.apache.jackrabbit.oak.query.index.FilterImpl;\nimport org.apache.jackrabbit.oak.spi.query.PropertyValue;\n\n/**\n * A fulltext \"contains(...)\" condition.\n */\npublic class FullTextSearchImpl extends ConstraintImpl {\n\n private final String selectorName;\n private final String propertyName;\n private final StaticOperandImpl fullTextSearchExpression;\n private SelectorImpl selector;\n\n public FullTextSearchImpl(String selectorName, String propertyName,\n StaticOperandImpl fullTextSearchExpression) {\n this.selectorName = selectorName;\n this.propertyName = propertyName;\n this.fullTextSearchExpression = fullTextSearchExpression;\n }\n\n public StaticOperandImpl getFullTextSearchExpression() {\n return fullTextSearchExpression;\n }\n\n @Override\n boolean accept(AstVisitor v) {\n return v.visit(this);\n }\n\n @Override\n public String toString() {\n StringBuilder builder = new StringBuilder();\n builder.append(\"contains(\");\n builder.append(quote(selectorName));\n if (propertyName != null) {\n builder.append('.');\n builder.append(quote(propertyName));\n builder.append(\", \");\n } else {\n builder.append(\".*, \");\n }\n builder.append(getFullTextSearchExpression());\n builder.append(')');\n return builder.toString();\n }\n\n @Override\n public boolean evaluate() {\n StringBuilder buff = new StringBuilder();\n if (propertyName != null) {\n PropertyValue p = selector.currentProperty(propertyName);\n if (p == null) {\n return false;\n }\n appendString(buff, p);\n } else {\n Tree tree = getTree(selector.currentPath());\n if (tree == null) {\n return false;\n }\n for (PropertyState p : tree.getProperties()) {\n appendString(buff, p);\n }\n }\n // TODO fulltext conditions: need a way to disable evaluation\n // if a fulltext index is used, to avoid filtering too much\n // (we don't know what exact options are used in the fulltext index)\n // (stop word, special characters,...)\n PropertyValue v = fullTextSearchExpression.currentValue();\n try {\n FullTextExpression expr = FullTextParser.parse(v.getValue(Type.STRING));\n return expr.evaluate(buff.toString());\n } catch (ParseException e) {\n throw new IllegalArgumentException(\"Invalid expression: \" + fullTextSearchExpression, e);\n }\n }\n\n private static void appendString(StringBuilder buff, PropertyState p) {\n if (p.isArray()) {\n for (String v : p.getValue(STRINGS)) {\n buff.append(v).append(' ');\n }\n } else {\n buff.append(p.getValue(STRING)).append(' ');\n }\n }\n\n public void bindSelector(SourceImpl source) {\n selector = source.getExistingSelector(selectorName);\n }\n\n @Override\n public void restrict(FilterImpl f) {\n if (propertyName != null) {\n if (f.getSelector() == selector) {\n f.restrictProperty(propertyName, Operator.NOT_EQUAL, null);\n }\n }\n f.restrictFulltextCondition(fullTextSearchExpression.currentValue().getValue(Type.STRING));\n }\n\n @Override\n public void restrictPushDown(SelectorImpl s) {\n if (s == selector) {\n selector.restrictSelector(this);\n }\n }\n\n /**\n * A parser for fulltext condition literals. The grammar is defined in the\n * \n * JCR 2.0 specification, 6.7.19 FullTextSearch,\n * as follows (a bit simplified):\n *
\n     * FullTextSearchLiteral ::= Disjunct {' OR ' Disjunct}\n     * Disjunct ::= Term {' ' Term}\n     * Term ::= ['-'] SimpleTerm\n     * SimpleTerm ::= Word | '\"' Word {' ' Word} '\"'\n     * 
\n */\n public static class FullTextParser {\n\n String text;\n int parseIndex;\n\n public static FullTextExpression parse(String text) throws ParseException {\n FullTextParser p = new FullTextParser();\n p.text = text;\n FullTextExpression e = p.parseOr();\n return e;\n }\n\n FullTextExpression parseOr() throws ParseException {\n FullTextOr or = new FullTextOr();\n or.list.add(parseAnd());\n while (parseIndex < text.length()) {\n if (text.substring(parseIndex).startsWith(\"OR \")) {\n parseIndex += 3;\n or.list.add(parseAnd());\n } else {\n break;\n }\n }\n return or.simplify();\n }\n\n FullTextExpression parseAnd() throws ParseException {\n FullTextAnd and = new FullTextAnd();\n and.list.add(parseTerm());\n while (parseIndex < text.length()) {\n if (text.substring(parseIndex).startsWith(\"OR \")) {\n break;\n }\n and.list.add(parseTerm());\n }\n return and.simplify();\n }\n\n FullTextExpression parseTerm() throws ParseException {\n if (parseIndex >= text.length()) {\n throw getSyntaxError(\"term\");\n }\n boolean not = false;\n StringBuilder buff = new StringBuilder();\n char c = text.charAt(parseIndex);\n if (c == '-') {\n if (++parseIndex >= text.length()) {\n throw getSyntaxError(\"term\");\n }\n not = true;\n }\n boolean escaped = false;\n if (c == '\\\"') {\n parseIndex++;\n while (true) {\n if (parseIndex >= text.length()) {\n throw getSyntaxError(\"double quote\");\n }\n c = text.charAt(parseIndex++);\n if (c == '\\\\') {\n escaped = true;\n if (parseIndex >= text.length()) {\n throw getSyntaxError(\"escaped char\");\n }\n c = text.charAt(parseIndex++);\n buff.append(c);\n } else if (c == '\\\"') {\n if (parseIndex < text.length() && text.charAt(parseIndex) != ' ') {\n throw getSyntaxError(\"space\");\n }\n parseIndex++;\n break;\n } else {\n buff.append(c);\n }\n }\n } else {\n do {\n c = text.charAt(parseIndex++);\n if (c == '\\\\') {\n escaped = true;\n if (parseIndex >= text.length()) {\n throw getSyntaxError(\"escaped char\");\n }\n c = text.charAt(parseIndex++);\n buff.append(c);\n } else if (c == ' ') {\n break;\n } else {\n buff.append(c);\n }\n } while (parseIndex < text.length());\n }\n if (buff.length() == 0) {\n throw getSyntaxError(\"term\");\n }\n String text = buff.toString();\n FullTextTerm term = new FullTextTerm(text, not, escaped);\n return term.simplify();\n }\n\n private ParseException getSyntaxError(String expected) {\n int index = Math.max(0, Math.min(parseIndex, text.length() - 1));\n String query = text.substring(0, index) + \"(*)\" + text.substring(index).trim();\n if (expected != null) {\n query += \"; expected: \" + expected;\n }\n return new ParseException(\"FullText expression: \" + query, index);\n }\n\n }\n\n /**\n * The base class for fulltext condition expression.\n */\n public abstract static class FullTextExpression {\n public abstract boolean evaluate(String value);\n abstract FullTextExpression simplify();\n }\n\n /**\n * A fulltext \"and\" condition.\n */\n static class FullTextAnd extends FullTextExpression {\n ArrayList list = new ArrayList();\n\n @Override\n public boolean evaluate(String value) {\n for (FullTextExpression e : list) {\n if (!e.evaluate(value)) {\n return false;\n }\n }\n return true;\n }\n\n @Override\n FullTextExpression simplify() {\n return list.size() == 1 ? list.get(0) : this;\n }\n\n @Override\n public String toString() {\n StringBuilder buff = new StringBuilder();\n int i = 0;\n for (FullTextExpression e : list) {\n if (i++ > 0) {\n buff.append(' ');\n }\n buff.append(e.toString());\n }\n return buff.toString();\n }\n\n }\n\n /**\n * A fulltext \"or\" condition.\n */\n static class FullTextOr extends FullTextExpression {\n ArrayList list = new ArrayList();\n\n @Override\n public boolean evaluate(String value) {\n for (FullTextExpression e : list) {\n if (e.evaluate(value)) {\n return true;\n }\n }\n return false;\n }\n\n @Override\n FullTextExpression simplify() {\n return list.size() == 1 ? list.get(0).simplify() : this;\n }\n\n @Override\n public String toString() {\n StringBuilder buff = new StringBuilder();\n int i = 0;\n for (FullTextExpression e : list) {\n if (i++ > 0) {\n buff.append(\" OR \");\n }\n buff.append(e.toString());\n }\n return buff.toString();\n }\n\n }\n\n /**\n * A fulltext term, or a \"not\" term.\n */\n static class FullTextTerm extends FullTextExpression {\n private final boolean not;\n private final String text;\n private final String filteredText;\n private final LikePattern like;\n\n FullTextTerm(String text, boolean not, boolean escaped) {\n this.text = text;\n this.not = not;\n // for testFulltextIntercapSQL\n // filter special characters such as '\n // to make tests pass, for example the\n // FulltextQueryTest.testFulltextExcludeSQL,\n // which searches for:\n // \"text ''fox jumps'' -other\"\n // (please note the two single quotes instead of\n // double quotes before for and after jumps)\n boolean pattern = false;\n if (escaped) {\n filteredText = text;\n } else {\n StringBuilder buff = new StringBuilder();\n for (int i = 0; i < text.length(); i++) {\n char c = text.charAt(i);\n if (c == '*') {\n buff.append('%');\n pattern = true;\n } else if (c == '?') {\n buff.append('_');\n pattern = true;\n } else if (c == '_') {\n buff.append(\"\\\\_\");\n pattern = true;\n } else if (Character.isLetterOrDigit(c) || \" +-:&\".indexOf(c) >= 0) {\n buff.append(c);\n }\n }\n this.filteredText = buff.toString().toLowerCase();\n }\n if (pattern) {\n like = new LikePattern(\"%\" + filteredText + \"%\");\n } else {\n like = null;\n }\n }\n\n @Override\n public boolean evaluate(String value) {\n // for testFulltextIntercapSQL\n value = value.toLowerCase();\n if (like != null) {\n return like.matches(value);\n }\n if (not) {\n return value.indexOf(filteredText) < 0;\n }\n return value.indexOf(filteredText) >= 0;\n }\n\n @Override\n FullTextExpression simplify() {\n return this;\n }\n\n @Override\n public String toString() {\n return (not ? \"-\" : \"\") + \"\\\"\" + text.replaceAll(\"\\\"\", \"\\\\\\\"\") + \"\\\"\";\n }\n\n }\n\n}\n =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java (revision c945219e1b9261b33382296da9d49bed60accebb) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java (revision ) @@ -18,9 +18,6 @@ */ package org.apache.jackrabbit.oak.query.ast; -import static org.apache.jackrabbit.oak.api.Type.STRING; -import static org.apache.jackrabbit.oak.api.Type.STRINGS; - import java.text.ParseException; import java.util.ArrayList; @@ -31,6 +28,9 @@ import org.apache.jackrabbit.oak.query.index.FilterImpl; import org.apache.jackrabbit.oak.spi.query.PropertyValue; +import static org.apache.jackrabbit.oak.api.Type.STRING; +import static org.apache.jackrabbit.oak.api.Type.STRINGS; + /** * A fulltext "contains(...)" condition. */ @@ -82,7 +82,7 @@ if (p == null) { return false; } - appendString(buff, p); + appendString(buff, p.unwrap()); } else { Tree tree = getTree(selector.currentPath()); if (tree == null) { Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/old/PropertyContentIndex.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\npackage org.apache.jackrabbit.oak.plugins.index.old;\n\nimport java.util.Iterator;\n\nimport org.apache.jackrabbit.oak.api.PropertyState;\nimport org.apache.jackrabbit.oak.api.Type;\nimport org.apache.jackrabbit.oak.query.index.IndexRowImpl;\nimport org.apache.jackrabbit.oak.spi.query.Cursor;\nimport org.apache.jackrabbit.oak.spi.query.Filter;\nimport org.apache.jackrabbit.oak.spi.query.IndexRow;\nimport org.apache.jackrabbit.oak.spi.query.QueryIndex;\nimport org.apache.jackrabbit.oak.spi.state.NodeState;\n\n/**\n * An index that stores the index data in a {@code MicroKernel}.\n * \n * @deprecated the revisionId info has been removed\n */\npublic class PropertyContentIndex implements QueryIndex {\n\n private final PropertyIndex index;\n\n public PropertyContentIndex(PropertyIndex index) {\n this.index = index;\n }\n\n @Override\n public double getCost(Filter filter) {\n String propertyName = index.getPropertyName();\n Filter.PropertyRestriction restriction = filter.getPropertyRestriction(propertyName);\n if (restriction == null) {\n return Double.MAX_VALUE;\n }\n if (restriction.first != restriction.last) {\n // only support equality matches (for now)\n return Double.MAX_VALUE;\n }\n boolean unique = index.isUnique();\n return unique ? 2 : 20;\n }\n\n @Override\n public String getPlan(Filter filter, NodeState root) {\n String propertyName = index.getPropertyName();\n Filter.PropertyRestriction restriction = filter.getPropertyRestriction(propertyName);\n return \"propertyIndex \\\"\" + restriction.propertyName + \" \" + restriction.toString() + '\"';\n }\n\n @Override\n public Cursor query(Filter filter, NodeState root) {\n String propertyName = index.getPropertyName();\n Filter.PropertyRestriction restriction = filter.getPropertyRestriction(propertyName);\n if (restriction == null) {\n throw new IllegalArgumentException(\"No restriction for \" + propertyName);\n }\n PropertyState first = restriction.first;\n String f = first == null ? null : first.getValue(Type.STRING);\n // TODO revisit code after the removal of revisionId\n String revisionId = \"\";\n Iterator it = index.getPaths(f, revisionId);\n return new ContentCursor(it);\n }\n\n\n @Override\n public String getIndexName() {\n return index.getIndexNodeName();\n }\n\n /**\n * The cursor to for this index.\n */\n static class ContentCursor implements Cursor {\n\n private final Iterator it;\n\n private String currentPath;\n\n public ContentCursor(Iterator it) {\n this.it = it;\n }\n\n @Override\n public IndexRow currentRow() {\n return new IndexRowImpl(currentPath);\n }\n\n @Override\n public boolean next() {\n if (it.hasNext()) {\n currentPath = it.next();\n return true;\n }\n return false;\n }\n }\n\n}\n =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/old/PropertyContentIndex.java (revision c945219e1b9261b33382296da9d49bed60accebb) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/old/PropertyContentIndex.java (revision ) @@ -20,12 +20,12 @@ import java.util.Iterator; -import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.query.index.IndexRowImpl; import org.apache.jackrabbit.oak.spi.query.Cursor; import org.apache.jackrabbit.oak.spi.query.Filter; import org.apache.jackrabbit.oak.spi.query.IndexRow; +import org.apache.jackrabbit.oak.spi.query.PropertyValue; import org.apache.jackrabbit.oak.spi.query.QueryIndex; import org.apache.jackrabbit.oak.spi.state.NodeState; @@ -71,7 +71,7 @@ if (restriction == null) { throw new IllegalArgumentException("No restriction for " + propertyName); } - PropertyState first = restriction.first; + PropertyValue first = restriction.first; String f = first == null ? null : first.getValue(Type.STRING); // TODO revisit code after the removal of revisionId String revisionId = ""; Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one or more\n * contributor license agreements. See the NOTICE file distributed with\n * this work for additional information regarding copyright ownership.\n * The ASF licenses this file to You under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.apache.jackrabbit.oak.plugins.index.property;\n\nimport java.util.Set;\n\nimport org.apache.jackrabbit.oak.api.PropertyState;\nimport org.apache.jackrabbit.oak.api.Type;\nimport org.apache.jackrabbit.oak.spi.query.PropertyValue;\nimport org.apache.jackrabbit.oak.spi.query.PropertyValues;\nimport org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;\nimport org.apache.jackrabbit.oak.spi.state.NodeState;\n\nimport com.google.common.collect.Sets;\n\npublic class PropertyIndexLookup {\n\n private final NodeState root;\n\n public PropertyIndexLookup(NodeState root) {\n this.root = root;\n }\n\n /**\n * Checks whether the named properties are indexed somewhere\n * along the given path.\n *\n * @param name property name\n * @param path lookup path\n */\n public boolean isIndexed(String name, String path) {\n NodeState state = root.getChildNode(\"oak:index\");\n if (state != null) {\n state = state.getChildNode(name);\n if (state != null) {\n return true;\n }\n }\n\n if (path.startsWith(\"/\")) {\n path = path.substring(1);\n }\n int slash = path.indexOf('/');\n if (slash == -1) {\n return false;\n }\n\n NodeState child = root.getChildNode(path.substring(0, slash));\n return new PropertyIndexLookup(child).isIndexed(\n name, path.substring(slash));\n }\n\n public Set find(String name, String value) {\n return find(name, PropertyValues.newString(value));\n }\n\n public Set find(String name, PropertyValue value) {\n Set paths = Sets.newHashSet();\n\n PropertyState property = null;\n NodeState state = root.getChildNode(\"oak:index\");\n if (state != null) {\n state = state.getChildNode(name);\n if (state != null) {\n state = state.getChildNode(\":index\");\n if (state != null) {\n //TODO what happens when I search using an mvp?\n property = state.getProperty(PropertyIndex.encode(value).get(0));\n }\n }\n }\n\n if (property != null) {\n // We have an index for this property, so use it\n for (String path : property.getValue(Type.STRINGS)) {\n paths.add(path);\n }\n } else {\n // No index available, so first check this node for a match\n property = root.getProperty(name);\n if (property != null){\n if(PropertyValues.match(property, value)){\n paths.add(\"\");\n }\n }\n\n // ... and then recursively look up from the rest of the tree\n for (ChildNodeEntry entry : root.getChildNodeEntries()) {\n String base = entry.getName();\n PropertyIndexLookup lookup =\n new PropertyIndexLookup(entry.getNodeState());\n for (String path : lookup.find(name, value)) {\n if (path.isEmpty()) {\n paths.add(base);\n } else {\n paths.add(base + \"/\" + path);\n }\n }\n }\n }\n\n return paths;\n }\n\n} =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java (revision c945219e1b9261b33382296da9d49bed60accebb) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java (revision ) @@ -79,7 +79,7 @@ state = state.getChildNode(":index"); if (state != null) { //TODO what happens when I search using an mvp? - property = state.getProperty(PropertyIndex.encode(value).get(0)); + property = state.getProperty(PropertyIndex.encode(value.unwrap()).get(0)); } } } \ No newline at end of file Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one or more\n * contributor license agreements. See the NOTICE file distributed with this\n * work for additional information regarding copyright ownership. The ASF\n * licenses this file to You under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law\n * or agreed to in writing, software distributed under the License is\n * distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\npackage org.apache.jackrabbit.oak.query;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\n\nimport org.apache.jackrabbit.oak.api.Root;\nimport org.apache.jackrabbit.oak.api.Tree;\nimport org.apache.jackrabbit.oak.namepath.NamePathMapper;\nimport org.apache.jackrabbit.oak.query.ast.AstVisitorBase;\nimport org.apache.jackrabbit.oak.query.ast.BindVariableValueImpl;\nimport org.apache.jackrabbit.oak.query.ast.ChildNodeImpl;\nimport org.apache.jackrabbit.oak.query.ast.ChildNodeJoinConditionImpl;\nimport org.apache.jackrabbit.oak.query.ast.ColumnImpl;\nimport org.apache.jackrabbit.oak.query.ast.ComparisonImpl;\nimport org.apache.jackrabbit.oak.query.ast.ConstraintImpl;\nimport org.apache.jackrabbit.oak.query.ast.DescendantNodeImpl;\nimport org.apache.jackrabbit.oak.query.ast.DescendantNodeJoinConditionImpl;\nimport org.apache.jackrabbit.oak.query.ast.EquiJoinConditionImpl;\nimport org.apache.jackrabbit.oak.query.ast.FullTextSearchImpl;\nimport org.apache.jackrabbit.oak.query.ast.FullTextSearchScoreImpl;\nimport org.apache.jackrabbit.oak.query.ast.LengthImpl;\nimport org.apache.jackrabbit.oak.query.ast.LiteralImpl;\nimport org.apache.jackrabbit.oak.query.ast.LowerCaseImpl;\nimport org.apache.jackrabbit.oak.query.ast.NodeLocalNameImpl;\nimport org.apache.jackrabbit.oak.query.ast.NodeNameImpl;\nimport org.apache.jackrabbit.oak.query.ast.OrderingImpl;\nimport org.apache.jackrabbit.oak.query.ast.PropertyExistenceImpl;\nimport org.apache.jackrabbit.oak.query.ast.PropertyValueImpl;\nimport org.apache.jackrabbit.oak.query.ast.SameNodeImpl;\nimport org.apache.jackrabbit.oak.query.ast.SameNodeJoinConditionImpl;\nimport org.apache.jackrabbit.oak.query.ast.SelectorImpl;\nimport org.apache.jackrabbit.oak.query.ast.SourceImpl;\nimport org.apache.jackrabbit.oak.query.ast.UpperCaseImpl;\nimport org.apache.jackrabbit.oak.spi.query.Filter;\nimport org.apache.jackrabbit.oak.spi.query.PropertyValue;\nimport org.apache.jackrabbit.oak.spi.query.PropertyValues;\nimport org.apache.jackrabbit.oak.spi.query.QueryIndex;\nimport org.apache.jackrabbit.oak.spi.state.NodeState;\n\n/**\n * Represents a parsed query. Lifecycle: use the constructor to create a new\n * object. Call init() to initialize the bind variable map. If the query is\n * re-executed, a new instance is created.\n */\npublic class Query {\n \n /**\n * The \"jcr:path\" pseudo-property.\n */\n // TODO jcr:path isn't an official feature, support it?\n public static final String JCR_PATH = \"jcr:path\";\n\n /**\n * The \"jcr:score\" pseudo-property.\n */\n public static final String JCR_SCORE = \"jcr:score\";\n\n final SourceImpl source;\n final ConstraintImpl constraint;\n final HashMap bindVariableMap = new HashMap();\n final HashMap selectorIndexes = new HashMap();\n final ArrayList selectors = new ArrayList();\n\n private QueryEngineImpl queryEngine;\n private final OrderingImpl[] orderings;\n private ColumnImpl[] columns;\n private boolean explain, measure;\n private long limit = Long.MAX_VALUE;\n private long offset;\n private long size = -1;\n private boolean prepared;\n private Root root;\n private NamePathMapper namePathMapper;\n\n Query(SourceImpl source, ConstraintImpl constraint, OrderingImpl[] orderings,\n ColumnImpl[] columns) {\n this.source = source;\n this.constraint = constraint;\n this.orderings = orderings;\n this.columns = columns;\n }\n\n public void init() {\n\n final Query query = this;\n\n new AstVisitorBase() {\n\n @Override\n public boolean visit(BindVariableValueImpl node) {\n node.setQuery(query);\n bindVariableMap.put(node.getBindVariableName(), null);\n return true;\n }\n\n @Override\n public boolean visit(ChildNodeImpl node) {\n node.setQuery(query);\n node.bindSelector(source);\n return true;\n }\n\n @Override\n public boolean visit(ChildNodeJoinConditionImpl node) {\n node.setQuery(query);\n node.bindSelector(source);\n return true;\n }\n\n @Override\n public boolean visit(ColumnImpl node) {\n node.setQuery(query);\n return true;\n }\n\n @Override\n public boolean visit(DescendantNodeImpl node) {\n node.setQuery(query);\n node.bindSelector(source);\n return true;\n }\n\n @Override\n public boolean visit(DescendantNodeJoinConditionImpl node) {\n node.setQuery(query);\n node.bindSelector(source);\n return true;\n }\n\n @Override\n public boolean visit(EquiJoinConditionImpl node) {\n node.setQuery(query);\n node.bindSelector(source);\n return true;\n }\n\n @Override\n public boolean visit(FullTextSearchImpl node) {\n node.setQuery(query);\n node.bindSelector(source);\n return super.visit(node);\n }\n\n @Override\n public boolean visit(FullTextSearchScoreImpl node) {\n node.setQuery(query);\n node.bindSelector(source);\n return true;\n }\n\n @Override\n public boolean visit(LiteralImpl node) {\n node.setQuery(query);\n return true;\n }\n\n @Override\n public boolean visit(NodeLocalNameImpl node) {\n node.setQuery(query);\n node.bindSelector(source);\n return true;\n }\n\n @Override\n public boolean visit(NodeNameImpl node) {\n node.setQuery(query);\n node.bindSelector(source);\n return true;\n }\n\n @Override\n public boolean visit(PropertyExistenceImpl node) {\n node.setQuery(query);\n node.bindSelector(source);\n return true;\n }\n\n @Override\n public boolean visit(PropertyValueImpl node) {\n node.setQuery(query);\n node.bindSelector(source);\n return true;\n }\n\n @Override\n public boolean visit(SameNodeImpl node) {\n node.setQuery(query);\n node.bindSelector(source);\n return true;\n }\n\n @Override\n public boolean visit(SameNodeJoinConditionImpl node) {\n node.setQuery(query);\n node.bindSelector(source);\n return true;\n }\n\n @Override\n public boolean visit(SelectorImpl node) {\n String name = node.getSelectorName();\n if (selectorIndexes.put(name, selectors.size()) != null) {\n throw new IllegalArgumentException(\"Two selectors with the same name: \" + name);\n }\n selectors.add(node);\n node.setQuery(query);\n return true;\n }\n\n @Override\n public boolean visit(LengthImpl node) {\n node.setQuery(query);\n return super.visit(node);\n }\n\n @Override\n public boolean visit(UpperCaseImpl node) {\n node.setQuery(query);\n return super.visit(node);\n }\n\n @Override\n public boolean visit(LowerCaseImpl node) {\n node.setQuery(query);\n return super.visit(node);\n }\n\n @Override\n public boolean visit(ComparisonImpl node) {\n node.setQuery(query);\n return super.visit(node);\n }\n\n }.visit(this);\n source.setQueryConstraint(constraint);\n source.init(this);\n for (ColumnImpl column : columns) {\n column.bindSelector(source);\n }\n }\n\n public ColumnImpl[] getColumns() {\n return columns;\n }\n\n public ConstraintImpl getConstraint() {\n return constraint;\n }\n\n public OrderingImpl[] getOrderings() {\n return orderings;\n }\n\n public SourceImpl getSource() {\n return source;\n }\n\n void bindValue(String varName, PropertyValue value) {\n bindVariableMap.put(varName, value);\n }\n\n public void setLimit(long limit) {\n this.limit = limit;\n }\n\n public void setOffset(long offset) {\n this.offset = offset;\n }\n\n public void setExplain(boolean explain) {\n this.explain = explain;\n }\n\n public void setMeasure(boolean measure) {\n this.measure = measure;\n }\n\n public ResultImpl executeQuery(NodeState root) {\n return new ResultImpl(this, root);\n }\n\n Iterator getRows(NodeState root) {\n prepare();\n Iterator it;\n if (explain) {\n String plan = source.getPlan(root);\n columns = new ColumnImpl[] { new ColumnImpl(\"explain\", \"plan\", \"plan\")};\n ResultRowImpl r = new ResultRowImpl(this,\n new String[0], \n new PropertyValue[] { PropertyValues.newString(plan)},\n null);\n it = Arrays.asList(r).iterator();\n } else {\n it = new RowIterator(root, limit, offset);\n long resultCount = 0;\n if (orderings != null) {\n // TODO \"order by\" is not necessary if the used index returns\n // rows in the same order\n ArrayList list = new ArrayList();\n while (it.hasNext()) {\n ResultRowImpl r = it.next();\n list.add(r);\n }\n resultCount = size = list.size();\n Collections.sort(list);\n it = list.iterator();\n } else if (measure) {\n while (it.hasNext()) {\n resultCount++;\n it.next();\n }\n }\n if (measure) {\n columns = new ColumnImpl[] {\n new ColumnImpl(\"measure\", \"selector\", \"selector\"),\n new ColumnImpl(\"measure\", \"scanCount\", \"scanCount\")\n };\n ArrayList list = new ArrayList();\n ResultRowImpl r = new ResultRowImpl(this,\n new String[0],\n new PropertyValue[] {\n PropertyValues.newString(\"query\"),\n PropertyValues.newLong(resultCount)\n },\n null);\n list.add(r);\n for (SelectorImpl selector : selectors) {\n r = new ResultRowImpl(this,\n new String[0],\n new PropertyValue[] {\n PropertyValues.newString(selector.getSelectorName()),\n PropertyValues.newLong(selector.getScanCount()),\n },\n null);\n list.add(r);\n }\n it = list.iterator();\n }\n }\n return it;\n }\n\n public int compareRows(PropertyValue[] orderValues,\n PropertyValue[] orderValues2) {\n int comp = 0;\n for (int i = 0, size = orderings.length; i < size; i++) {\n PropertyValue a = orderValues[i];\n PropertyValue b = orderValues2[i];\n if (a == null || b == null) {\n if (a == b) {\n comp = 0;\n } else if (a == null) {\n // TODO order by: nulls first (it looks like), or low?\n comp = -1;\n } else {\n comp = 1;\n }\n } else {\n comp = a.compareTo(b);\n }\n if (comp != 0) {\n if (orderings[i].isDescending()) {\n comp = -comp;\n }\n break;\n }\n }\n return comp;\n }\n\n void prepare() {\n if (prepared) {\n return;\n }\n prepared = true;\n source.prepare();\n }\n\n /**\n * An iterator over result rows.\n */\n class RowIterator implements Iterator {\n\n private final NodeState root;\n private ResultRowImpl current;\n private boolean started, end;\n private long limit, offset, rowIndex;\n\n RowIterator(NodeState root, long limit, long offset) {\n this.root = root;\n this.limit = limit;\n this.offset = offset;\n }\n\n private void fetchNext() {\n if (end) {\n return;\n }\n if (rowIndex >= limit) {\n end = true;\n return;\n }\n if (!started) {\n source.execute(root);\n started = true;\n }\n while (true) {\n if (source.next()) {\n if (constraint == null || constraint.evaluate()) {\n if (offset > 0) {\n offset--;\n continue;\n }\n current = currentRow();\n rowIndex++;\n break;\n }\n } else {\n current = null;\n end = true;\n break;\n }\n }\n }\n\n @Override\n public boolean hasNext() {\n if (end) {\n return false;\n }\n if (current == null) {\n fetchNext();\n }\n return !end;\n }\n\n @Override\n public ResultRowImpl next() {\n if (end) {\n return null;\n }\n if (current == null) {\n fetchNext();\n }\n ResultRowImpl r = current;\n current = null;\n return r;\n }\n\n @Override\n public void remove() {\n throw new UnsupportedOperationException();\n }\n\n }\n\n ResultRowImpl currentRow() {\n int selectorCount = selectors.size();\n String[] paths = new String[selectorCount];\n for (int i = 0; i < selectorCount; i++) {\n SelectorImpl s = selectors.get(i);\n paths[i] = s.currentPath();\n }\n int columnCount = columns.length;\n PropertyValue[] values = new PropertyValue[columnCount];\n for (int i = 0; i < columnCount; i++) {\n ColumnImpl c = columns[i];\n values[i] = PropertyValues.create(c.currentProperty());\n }\n PropertyValue[] orderValues;\n if (orderings == null) {\n orderValues = null;\n } else {\n int size = orderings.length;\n orderValues = new PropertyValue[size];\n for (int i = 0; i < size; i++) {\n orderValues[i] = PropertyValues.create(orderings[i].getOperand().currentProperty());\n }\n }\n return new ResultRowImpl(this, paths, values, orderValues);\n }\n\n public int getSelectorIndex(String selectorName) {\n Integer index = selectorIndexes.get(selectorName);\n if (index == null) {\n throw new IllegalArgumentException(\"Unknown selector: \" + selectorName);\n }\n return index;\n }\n\n public int getColumnIndex(String columnName) {\n for (int i = 0, size = columns.length; i < size; i++) {\n ColumnImpl c = columns[i];\n String cn = c.getColumnName();\n if (cn != null && cn.equals(columnName)) {\n return i;\n }\n }\n throw new IllegalArgumentException(\"Column not found: \" + columnName);\n }\n\n public PropertyValue getBindVariableValue(String bindVariableName) {\n PropertyValue v = bindVariableMap.get(bindVariableName);\n if (v == null) {\n throw new IllegalArgumentException(\"Bind variable value not set: \" + bindVariableName);\n }\n return v;\n }\n\n public List getSelectors() {\n return Collections.unmodifiableList(selectors);\n }\n\n public List getBindVariableNames() {\n return new ArrayList(bindVariableMap.keySet());\n }\n\n public void setQueryEngine(QueryEngineImpl queryEngine) {\n this.queryEngine = queryEngine;\n }\n\n public QueryIndex getBestIndex(Filter filter) {\n return queryEngine.getBestIndex(filter);\n }\n\n public void setRoot(Root root) {\n this.root = root;\n }\n\n public void setNamePathMapper(NamePathMapper namePathMapper) {\n this.namePathMapper = namePathMapper;\n }\n\n public NamePathMapper getNamePathMapper() {\n return namePathMapper;\n }\n\n public Tree getTree(String path) {\n return root.getTree(path);\n }\n\n @Override\n public String toString() {\n StringBuilder buff = new StringBuilder();\n buff.append(\"select \");\n int i = 0;\n for (ColumnImpl c : columns) {\n if (i++ > 0) {\n buff.append(\", \");\n }\n buff.append(c);\n }\n buff.append(\" from \").append(source);\n if (constraint != null) {\n buff.append(\" where \").append(constraint);\n }\n if (orderings != null) {\n buff.append(\" order by \");\n i = 0;\n for (OrderingImpl o : orderings) {\n if (i++ > 0) {\n buff.append(\", \");\n }\n buff.append(o);\n }\n }\n return buff.toString();\n }\n\n public long getSize() {\n return size;\n }\n\n}\n =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java (revision c945219e1b9261b33382296da9d49bed60accebb) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java (revision ) @@ -482,7 +482,7 @@ PropertyValue[] values = new PropertyValue[columnCount]; for (int i = 0; i < columnCount; i++) { ColumnImpl c = columns[i]; - values[i] = PropertyValues.create(c.currentProperty()); + values[i] = c.currentProperty(); } PropertyValue[] orderValues; if (orderings == null) { @@ -491,7 +491,7 @@ int size = orderings.length; orderValues = new PropertyValue[size]; for (int i = 0; i < size; i++) { - orderValues[i] = PropertyValues.create(orderings[i].getOperand().currentProperty()); + orderValues[i] = orderings[i].getOperand().currentProperty(); } } return new ResultRowImpl(this, paths, values, orderValues); Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/UpperCaseImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\npackage org.apache.jackrabbit.oak.query.ast;\n\nimport static org.apache.jackrabbit.oak.api.Type.STRING;\n\nimport org.apache.jackrabbit.oak.api.PropertyState;\nimport org.apache.jackrabbit.oak.query.index.FilterImpl;\nimport org.apache.jackrabbit.oak.spi.query.PropertyValue;\nimport org.apache.jackrabbit.oak.spi.query.PropertyValues;\n\n/**\n * The function \"upper(..)\".\n */\npublic class UpperCaseImpl extends DynamicOperandImpl {\n\n private final DynamicOperandImpl operand;\n\n public UpperCaseImpl(DynamicOperandImpl operand) {\n this.operand = operand;\n }\n\n public DynamicOperandImpl getOperand() {\n return operand;\n }\n\n @Override\n boolean accept(AstVisitor v) {\n return v.visit(this);\n }\n\n @Override\n public String toString() {\n return \"upper(\" + operand + ')';\n }\n\n @Override\n public PropertyValue currentProperty() {\n PropertyState p = operand.currentProperty();\n if (p == null) {\n return null;\n }\n // TODO what is the expected result of UPPER(x) for an array property?\n // currently throws an exception\n String value = p.getValue(STRING);\n return PropertyValues.newString(value.toUpperCase());\n }\n\n @Override\n public void restrict(FilterImpl f, Operator operator, PropertyValue v) {\n // UPPER(x) implies x is not null\n operand.restrict(f, Operator.NOT_EQUAL, null);\n }\n\n @Override\n public boolean canRestrictSelector(SelectorImpl s) {\n return operand.canRestrictSelector(s);\n }\n\n}\n =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/UpperCaseImpl.java (revision c945219e1b9261b33382296da9d49bed60accebb) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/UpperCaseImpl.java (revision ) @@ -18,13 +18,12 @@ */ package org.apache.jackrabbit.oak.query.ast; -import static org.apache.jackrabbit.oak.api.Type.STRING; - -import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.query.index.FilterImpl; import org.apache.jackrabbit.oak.spi.query.PropertyValue; import org.apache.jackrabbit.oak.spi.query.PropertyValues; +import static org.apache.jackrabbit.oak.api.Type.STRING; + /** * The function "upper(..)". */ @@ -52,7 +51,7 @@ @Override public PropertyValue currentProperty() { - PropertyState p = operand.currentProperty(); + PropertyValue p = operand.currentProperty(); if (p == null) { return null; } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/PropertyValues.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\npackage org.apache.jackrabbit.oak.spi.query;\n\nimport java.io.IOException;\nimport java.math.BigDecimal;\nimport java.util.Iterator;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nonnull;\nimport javax.jcr.PropertyType;\n\nimport org.apache.jackrabbit.oak.api.PropertyState;\nimport org.apache.jackrabbit.oak.api.Type;\nimport org.apache.jackrabbit.oak.namepath.NamePathMapper;\nimport org.apache.jackrabbit.oak.plugins.memory.PropertyStates;\n\npublic class PropertyValues {\n\n private PropertyValues() {\n }\n\n // TODO consistent naming\n\n @CheckForNull\n public static PropertyValue create(PropertyState property) {\n if (property == null) {\n return null;\n }\n return new PropertyValue(property);\n }\n\n @Nonnull\n public static PropertyValue newString(String value) {\n return new PropertyValue(PropertyStates.stringProperty(\"\", value));\n }\n\n @Nonnull\n public static PropertyValue newString(Iterable value) {\n return new PropertyValue(PropertyStates.stringProperty(\"\", value));\n }\n\n @Nonnull\n public static PropertyValue newLong(Long value) {\n return new PropertyValue(PropertyStates.longProperty(\"\", value));\n }\n\n @Nonnull\n public static PropertyValue newDouble(Double value) {\n return new PropertyValue(PropertyStates.doubleProperty(\"\", value));\n }\n\n @Nonnull\n public static PropertyValue newDecimal(BigDecimal value) {\n return new PropertyValue(PropertyStates.decimalProperty(\"\", value));\n }\n\n @Nonnull\n public static PropertyValue newBoolean(boolean value) {\n return new PropertyValue(PropertyStates.booleanProperty(\"\", value));\n }\n\n @Nonnull\n public static PropertyValue newDate(String value) {\n return new PropertyValue(PropertyStates.dateProperty(\"\", value));\n }\n\n @Nonnull\n public static PropertyValue newName(String value) {\n return new PropertyValue(PropertyStates.nameProperty(\"\", value));\n }\n\n @Nonnull\n public static PropertyValue newPath(String value) {\n return new PropertyValue(PropertyStates.pathProperty(\"\", value));\n }\n\n @Nonnull\n public static PropertyValue newReference(String value) {\n return new PropertyValue(PropertyStates.referenceProperty(\"\", value));\n }\n\n @Nonnull\n public static PropertyValue newWeakReference(String value) {\n return new PropertyValue(\n PropertyStates.weakreferenceProperty(\"\", value));\n }\n\n @Nonnull\n public static PropertyValue newUri(String value) {\n return new PropertyValue(PropertyStates.uriProperty(\"\", value));\n }\n\n @Nonnull\n public static PropertyValue newBinary(byte[] value) {\n return new PropertyValue(PropertyStates.binaryProperty(\"\", value));\n }\n\n // --\n\n public static boolean match(PropertyValue p1, PropertyState p2) {\n return match(p1, create(p2));\n }\n\n public static boolean match(PropertyState p1, PropertyValue p2) {\n return match(create(p1), p2);\n }\n\n public static boolean match(PropertyValue p1, PropertyValue p2) {\n if (p1.getType().tag() != p2.getType().tag()) {\n return false;\n }\n\n switch (p1.getType().tag()) {\n case PropertyType.BINARY:\n if (p1.isArray() && !p2.isArray()) {\n return contains(p1.getValue(Type.BINARIES),\n p2.getValue(Type.BINARY));\n }\n if (!p1.isArray() && p2.isArray()) {\n return contains(p2.getValue(Type.BINARIES),\n p2.getValue(Type.BINARY));\n }\n default:\n if (p1.isArray() && !p2.isArray()) {\n return contains(p1.getValue(Type.STRINGS),\n p2.getValue(Type.STRING));\n }\n if (!p1.isArray() && p2.isArray()) {\n return contains(p2.getValue(Type.STRINGS),\n p2.getValue(Type.STRING));\n }\n }\n // both arrays or both single values\n return p1.compareTo(p2) == 0;\n\n }\n\n private static > boolean contains(Iterable p1,\n T p2) {\n Iterator i1 = p1.iterator();\n while (i1.hasNext()) {\n int compare = i1.next().compareTo(p2);\n if (compare == 0) {\n return true;\n }\n }\n return false;\n }\n\n // --\n /**\n * Convert a value to the given target type, if possible.\n * \n * @param v\n * the value to convert\n * @param targetType\n * the target property type\n * @return the converted value, or null if converting is not possible\n */\n public static PropertyValue convert(PropertyState value, int targetType,\n NamePathMapper mapper) {\n // TODO support full set of conversion features defined in the JCR spec\n // at 3.6.4 Property Type Conversion\n // re-use existing code if possible\n try {\n switch (targetType) {\n case PropertyType.STRING:\n return newString(value.getValue(Type.STRING));\n case PropertyType.DATE:\n return newDate(value.getValue(Type.STRING));\n case PropertyType.LONG:\n return newLong(value.getValue(Type.LONG));\n case PropertyType.DOUBLE:\n return newDouble(value.getValue(Type.DOUBLE));\n case PropertyType.DECIMAL:\n return newDecimal(value.getValue(Type.DECIMAL));\n case PropertyType.BOOLEAN:\n return newBoolean(value.getValue(Type.BOOLEAN));\n case PropertyType.NAME:\n return newName(getOakPath(value.getValue(Type.STRING), mapper));\n case PropertyType.PATH:\n return newPath(value.getValue(Type.STRING));\n case PropertyType.REFERENCE:\n return newReference(value.getValue(Type.STRING));\n case PropertyType.WEAKREFERENCE:\n return newWeakReference(value.getValue(Type.STRING));\n case PropertyType.URI:\n return newUri(value.getValue(Type.STRING));\n case PropertyType.BINARY:\n try {\n byte[] data = value.getValue(Type.STRING).getBytes(\"UTF-8\");\n return newBinary(data);\n } catch (IOException e) {\n // I don't know in what case that could really occur\n // except if UTF-8 isn't supported\n throw new IllegalArgumentException(\n value.getValue(Type.STRING), e);\n }\n }\n return null;\n // throw new IllegalArgumentException(\"Unknown property type: \" +\n // targetType);\n } catch (UnsupportedOperationException e) {\n // TODO detect unsupported conversions, so that no exception is\n // thrown\n // because exceptions are slow\n return null;\n // throw new IllegalArgumentException(\"\");\n }\n }\n\n public static String getOakPath(String jcrPath, NamePathMapper mapper) {\n if (mapper == null) {\n // to simplify testing, a getNamePathMapper isn't required\n return jcrPath;\n }\n String p = mapper.getOakPath(jcrPath);\n if (p == null) {\n throw new IllegalArgumentException(\"Not a valid JCR path: \"\n + jcrPath);\n }\n return p;\n }\n\n}\n =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/PropertyValues.java (revision c945219e1b9261b33382296da9d49bed60accebb) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/PropertyValues.java (revision ) @@ -168,13 +168,13 @@ /** * Convert a value to the given target type, if possible. * - * @param v + * @param value * the value to convert * @param targetType * the target property type * @return the converted value, or null if converting is not possible */ - public static PropertyValue convert(PropertyState value, int targetType, + public static PropertyValue convert(PropertyValue value, int targetType, NamePathMapper mapper) { // TODO support full set of conversion features defined in the JCR spec // at 3.6.4 Property Type Conversion Index: oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/QueryResultImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\npackage org.apache.jackrabbit.oak.jcr.query;\n\nimport java.util.Iterator;\nimport java.util.NoSuchElementException;\n\nimport javax.annotation.CheckForNull;\nimport javax.jcr.NodeIterator;\nimport javax.jcr.RepositoryException;\nimport javax.jcr.Value;\nimport javax.jcr.query.QueryResult;\nimport javax.jcr.query.RowIterator;\n\nimport org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter;\nimport org.apache.jackrabbit.commons.iterator.RowIteratorAdapter;\nimport org.apache.jackrabbit.oak.api.Result;\nimport org.apache.jackrabbit.oak.api.ResultRow;\nimport org.apache.jackrabbit.oak.commons.PathUtils;\nimport org.apache.jackrabbit.oak.jcr.NodeDelegate;\nimport org.apache.jackrabbit.oak.jcr.NodeImpl;\nimport org.apache.jackrabbit.oak.jcr.SessionDelegate;\nimport org.apache.jackrabbit.oak.plugins.memory.CoreValues;\nimport org.apache.jackrabbit.oak.spi.query.PropertyValue;\n\n/**\n * The implementation of the corresponding JCR interface.\n */\npublic class QueryResultImpl implements QueryResult {\n \n /**\n * The minimum number of rows / nodes to pre-fetch.\n */ \n private static final int PREFETCH_MIN = 20;\n\n /**\n * The maximum number of rows / nodes to pre-fetch.\n */\n private static final int PREFETCH_MAX = 100;\n\n /**\n * The maximum number of milliseconds to prefetch rows / nodes.\n */\n private static final int PREFETCH_TIMEOUT = 100;\n\n final SessionDelegate sessionDelegate;\n final Result result;\n final String pathFilter;\n\n public QueryResultImpl(SessionDelegate sessionDelegate, Result result) {\n this.sessionDelegate = sessionDelegate;\n this.result = result;\n\n // TODO the path currently contains the workspace name\n // TODO filter in oak-core once we support workspaces there\n pathFilter = \"/\";\n }\n\n @Override\n public String[] getColumnNames() throws RepositoryException {\n return result.getColumnNames();\n }\n\n @Override\n public String[] getSelectorNames() {\n return result.getSelectorNames();\n }\n\n boolean includeRow(String path) {\n if (path == null) {\n // a row without path (explain,...)\n return true;\n }\n if (PathUtils.isAncestor(pathFilter, path) || pathFilter.equals(path)) {\n // a row within this workspace\n return true;\n }\n return false;\n }\n\n @Override\n public RowIterator getRows() throws RepositoryException {\n Iterator rowIterator = new Iterator() {\n\n private final Iterator it = result.getRows().iterator();\n private RowImpl current;\n\n {\n fetch();\n }\n\n private void fetch() {\n current = null;\n while (it.hasNext()) {\n ResultRow r = it.next();\n for (String s : getSelectorNames()) {\n String path = r.getPath(s);\n if (includeRow(path)) {\n current = new RowImpl(QueryResultImpl.this, r);\n return;\n }\n }\n }\n }\n\n @Override\n public boolean hasNext() {\n return current != null;\n }\n\n @Override\n public RowImpl next() {\n if (current == null) {\n throw new NoSuchElementException();\n }\n RowImpl r = current;\n fetch();\n return r;\n }\n\n @Override\n public void remove() {\n throw new UnsupportedOperationException();\n }\n\n };\n final PrefetchIterator prefIt = new PrefetchIterator(\n rowIterator, \n PREFETCH_MIN, PREFETCH_TIMEOUT, PREFETCH_MAX, \n result.getSize());\n return new RowIteratorAdapter(prefIt) {\n @Override\n public long getSize() {\n return prefIt.size();\n }\n };\n }\n\n @CheckForNull\n NodeImpl getNode(String path) {\n NodeDelegate d = sessionDelegate.getNode(path);\n return d == null ? null : new NodeImpl(d);\n }\n\n String getLocalPath(String path) {\n return PathUtils.concat(\"/\", PathUtils.relativize(pathFilter, path));\n }\n\n @Override\n public NodeIterator getNodes() throws RepositoryException {\n String[] selectorNames = getSelectorNames();\n final String selectorName = selectorNames[0];\n if (getSelectorNames().length > 1) {\n // use the first selector\n // TODO verify using the first selector is allowed according to the specification,\n // otherwise just allow it when using XPath queries, or make XPath queries\n // look like they only contain one selector\n // throw new RepositoryException(\"Query contains more than one selector: \" +\n // Arrays.toString(getSelectorNames()));\n }\n Iterator nodeIterator = new Iterator() {\n\n private final Iterator it = result.getRows().iterator();\n private NodeImpl current;\n\n {\n fetch();\n }\n\n private void fetch() {\n current = null;\n while (it.hasNext()) {\n ResultRow r = it.next();\n String path = r.getPath(selectorName);\n if (includeRow(path)) {\n current = getNode(getLocalPath(path));\n break;\n }\n }\n }\n\n @Override\n public boolean hasNext() {\n return current != null;\n }\n\n @Override\n public NodeImpl next() {\n if (current == null) {\n throw new NoSuchElementException();\n }\n NodeImpl n = current;\n fetch();\n return n;\n }\n\n @Override\n public void remove() {\n throw new UnsupportedOperationException();\n }\n\n };\n final PrefetchIterator prefIt = new PrefetchIterator(\n nodeIterator, \n PREFETCH_MIN, PREFETCH_TIMEOUT, PREFETCH_MAX, \n result.getSize());\n return new NodeIteratorAdapter(prefIt) {\n @Override\n public long getSize() {\n return prefIt.size();\n }\n };\n }\n\n Value createValue(PropertyValue value) {\n return value == null ? null : sessionDelegate.getValueFactory().createValue(CoreValues.getValue(value));\n }\n\n}\n =================================================================== --- oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/QueryResultImpl.java (revision c945219e1b9261b33382296da9d49bed60accebb) +++ oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/QueryResultImpl.java (revision ) @@ -229,7 +229,7 @@ } Value createValue(PropertyValue value) { - return value == null ? null : sessionDelegate.getValueFactory().createValue(CoreValues.getValue(value)); + return value == null ? null : sessionDelegate.getValueFactory().createValue(CoreValues.getValue(value.unwrap())); } } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\npackage org.apache.jackrabbit.oak.query.ast;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Locale;\n\nimport javax.jcr.PropertyType;\n\nimport org.apache.jackrabbit.oak.api.PropertyState;\nimport org.apache.jackrabbit.oak.api.Tree;\nimport org.apache.jackrabbit.oak.api.Type;\nimport org.apache.jackrabbit.oak.commons.PathUtils;\nimport org.apache.jackrabbit.oak.query.SQL2Parser;\nimport org.apache.jackrabbit.oak.query.index.FilterImpl;\nimport org.apache.jackrabbit.oak.spi.query.PropertyValue;\nimport org.apache.jackrabbit.oak.spi.query.PropertyValues;\n\nimport com.google.common.collect.Iterables;\n\n/**\n * A property expression.\n */\npublic class PropertyValueImpl extends DynamicOperandImpl {\n\n private final String selectorName;\n private final String propertyName;\n private final int propertyType;\n private SelectorImpl selector;\n\n public PropertyValueImpl(String selectorName, String propertyName) {\n this(selectorName, propertyName, null);\n }\n\n public PropertyValueImpl(String selectorName, String propertyName, String propertyType) {\n this.selectorName = selectorName;\n this.propertyName = propertyName;\n this.propertyType = propertyType == null ?\n PropertyType.UNDEFINED :\n SQL2Parser.getPropertyTypeFromName(propertyType);\n }\n\n public String getSelectorName() {\n return selectorName;\n }\n\n public String getPropertyName() {\n return propertyName;\n }\n\n @Override\n boolean accept(AstVisitor v) {\n return v.visit(this);\n }\n\n @Override\n public String toString() {\n String s = quote(selectorName) + '.' + quote(propertyName);\n if (propertyType != PropertyType.UNDEFINED) {\n s = \"property(\" + s + \", '\" +\n PropertyType.nameFromValue(propertyType).toLowerCase(Locale.ENGLISH) +\n \"')\";\n }\n return s;\n }\n\n @Override\n public PropertyValue currentProperty() {\n boolean relative = propertyName.indexOf('/') >= 0;\n boolean asterisk = propertyName.equals(\"*\");\n if (!relative && !asterisk) {\n PropertyValue p = selector.currentProperty(propertyName);\n return matchesPropertyType(p) ? p : null;\n }\n Tree tree = getTree(selector.currentPath());\n if (tree == null) {\n return null;\n }\n if (relative) {\n for (String p : PathUtils.elements(PathUtils.getParentPath(propertyName))) {\n if (tree == null) {\n return null;\n }\n if (!tree.hasChild(p)) {\n return null;\n }\n tree = tree.getChild(p);\n }\n if (tree == null) {\n return null;\n }\n }\n if (!asterisk) {\n String name = PathUtils.getName(propertyName);\n if (!tree.hasProperty(name)) {\n return null;\n }\n PropertyState p = tree.getProperty(name);\n return matchesPropertyType(p) ? PropertyValues.create(p) : null;\n }\n // asterisk - create a multi-value property\n // warning: the returned property state may have a mixed type\n // (not all values may have the same type)\n\n //TODO this doesn't play well with the idea that the types may be different\n List values = new ArrayList();\n for (PropertyState p : tree.getProperties()) {\n if (matchesPropertyType(p)) {\n Iterables.addAll(values, p.getValue(Type.STRINGS));\n }\n }\n // \"*\"\n return PropertyValues.newString(values);\n }\n\n private boolean matchesPropertyType(PropertyState state) {\n if (state == null) {\n return false;\n }\n if (propertyType == PropertyType.UNDEFINED) {\n return true;\n }\n return state.getType().tag() == propertyType;\n }\n\n public void bindSelector(SourceImpl source) {\n selector = source.getExistingSelector(selectorName);\n }\n\n @Override\n public void restrict(FilterImpl f, Operator operator, PropertyValue v) {\n if (f.getSelector() == selector) {\n if (operator == Operator.NOT_EQUAL && v != null) {\n // not supported\n return;\n }\n f.restrictProperty(propertyName, operator, v);\n if (propertyType != PropertyType.UNDEFINED) {\n f.restrictPropertyType(propertyName, operator, propertyType);\n }\n }\n }\n\n @Override\n public boolean canRestrictSelector(SelectorImpl s) {\n return s == selector;\n }\n\n}\n =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java (revision c945219e1b9261b33382296da9d49bed60accebb) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java (revision ) @@ -87,7 +87,7 @@ boolean asterisk = propertyName.equals("*"); if (!relative && !asterisk) { PropertyValue p = selector.currentProperty(propertyName); - return matchesPropertyType(p) ? p : null; + return matchesPropertyType(p.unwrap()) ? p : null; } Tree tree = getTree(selector.currentPath()); if (tree == null) { Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LowerCaseImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\npackage org.apache.jackrabbit.oak.query.ast;\n\nimport static org.apache.jackrabbit.oak.api.Type.STRING;\n\nimport org.apache.jackrabbit.oak.api.PropertyState;\nimport org.apache.jackrabbit.oak.query.index.FilterImpl;\nimport org.apache.jackrabbit.oak.spi.query.PropertyValue;\nimport org.apache.jackrabbit.oak.spi.query.PropertyValues;\n\n/**\n * The function \"lower(..)\".\n */\npublic class LowerCaseImpl extends DynamicOperandImpl {\n\n private final DynamicOperandImpl operand;\n\n public LowerCaseImpl(DynamicOperandImpl operand) {\n this.operand = operand;\n }\n\n public DynamicOperandImpl getOperand() {\n return operand;\n }\n\n @Override\n boolean accept(AstVisitor v) {\n return v.visit(this);\n }\n\n @Override\n public String toString() {\n return \"lower(\" + operand + ')';\n }\n\n @Override\n public PropertyValue currentProperty() {\n PropertyState p = operand.currentProperty();\n if (p == null) {\n return null;\n }\n // TODO what is the expected result of LOWER(x) for an array property?\n // currently throws an exception\n String value = p.getValue(STRING);\n return PropertyValues.newString(value.toLowerCase());\n }\n\n @Override\n public void restrict(FilterImpl f, Operator operator, PropertyValue v) {\n // LOWER(x) implies x is not null\n operand.restrict(f, Operator.NOT_EQUAL, null);\n }\n\n @Override\n public boolean canRestrictSelector(SelectorImpl s) {\n return operand.canRestrictSelector(s);\n }\n\n}\n =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LowerCaseImpl.java (revision c945219e1b9261b33382296da9d49bed60accebb) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LowerCaseImpl.java (revision ) @@ -18,13 +18,12 @@ */ package org.apache.jackrabbit.oak.query.ast; -import static org.apache.jackrabbit.oak.api.Type.STRING; - -import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.query.index.FilterImpl; import org.apache.jackrabbit.oak.spi.query.PropertyValue; import org.apache.jackrabbit.oak.spi.query.PropertyValues; +import static org.apache.jackrabbit.oak.api.Type.STRING; + /** * The function "lower(..)". */ @@ -52,7 +51,7 @@ @Override public PropertyValue currentProperty() { - PropertyState p = operand.currentProperty(); + PropertyValue p = operand.currentProperty(); if (p == null) { return null; }