You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by th...@apache.org on 2012/08/30 11:20:10 UTC

svn commit: r1378867 - in /jackrabbit/oak/trunk: oak-core/src/main/java/org/apache/jackrabbit/mk/wrapper/ oak-core/src/main/java/org/apache/jackrabbit/oak/api/ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/ oak-core/src/main/java/org/...

Author: thomasm
Date: Thu Aug 30 09:20:10 2012
New Revision: 1378867

URL: http://svn.apache.org/viewvc?rev=1378867&view=rev
Log:
OAK-237 Run Jackrabbit query tests - implement missing features and improve compatibility with Jackrabbit 2.x (fulltext search, result size, join and order by multi-valued properties, order by nulls first, order by value type, compatibility with Jackrabbit 2.x identifiers in SQL-1, node name ISO9075 escaping)

Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/wrapper/BranchMergeMicroKernel.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/Result.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/MemoryValue.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ResultImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ResultRowImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SQL2Parser.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/XPathToSQL2Converter.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElement.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ComparisonImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DynamicOperandImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/EquiJoinConditionImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NodeLocalNameImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NodeNameImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java
    jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/sql2.txt
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/QueryResultImpl.java
    jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/wrapper/BranchMergeMicroKernel.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/wrapper/BranchMergeMicroKernel.java?rev=1378867&r1=1378866&r2=1378867&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/wrapper/BranchMergeMicroKernel.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/wrapper/BranchMergeMicroKernel.java Thu Aug 30 09:20:10 2012
@@ -66,6 +66,9 @@ public class BranchMergeMicroKernel impl
     }
 
     private static String getBranchId(String revisionId) {
+        if (revisionId == null) {
+            return TRUNK;
+        }
         int idx = revisionId.indexOf('-');
         if (idx <= 0) {
             return TRUNK;
@@ -129,7 +132,7 @@ public class BranchMergeMicroKernel impl
     public long getChildNodeCount(String path, String revisionId) {
         String branch = getBranchId(revisionId);
         String rev = getRevisionId(branch, revisionId);
-        return getChildNodeCount(path, rev);
+        return base.getChildNodeCount(path, rev);
     }
 
     @Override

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/Result.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/Result.java?rev=1378867&r1=1378866&r2=1378867&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/Result.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/Result.java Thu Aug 30 09:20:10 2012
@@ -21,10 +21,33 @@ package org.apache.jackrabbit.oak.api;
  */
 public interface Result {
 
+    /**
+     * Get the list of column names.
+     *
+     * @return the column names
+     */
     String[] getColumnNames();
 
+    /**
+     * Get the list of selector names.
+     *
+     * @return the selector names
+     */
     String[] getSelectorNames();
 
+    /**
+     * Get the rows.
+     *
+     * @return the rows
+     */
     Iterable<? extends ResultRow> getRows();
 
+    /**
+     * Get the number of rows, if known. If the size is not known, -1 is
+     * returned.
+     *
+     * @return the size or -1 if unknown
+     */
+    long getSize();
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/MemoryValue.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/MemoryValue.java?rev=1378867&r1=1378866&r2=1378867&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/MemoryValue.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/MemoryValue.java Thu Aug 30 09:20:10 2012
@@ -73,7 +73,7 @@ public abstract class MemoryValue implem
 
         int type = getType();
         if (type != o.getType()) {
-            return type - o.getType();
+            return o.getType() - type;
         } else if (type == PropertyType.LONG) {
             return Long.signum(getLong() - o.getLong());
         } else if (type == PropertyType.DOUBLE) {

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java?rev=1378867&r1=1378866&r2=1378867&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java Thu Aug 30 09:20:10 2012
@@ -13,12 +13,15 @@
  */
 package org.apache.jackrabbit.oak.query;
 
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import javax.jcr.PropertyType;
 import org.apache.jackrabbit.mk.api.MicroKernel;
 import org.apache.jackrabbit.oak.api.ContentSession;
 import org.apache.jackrabbit.oak.api.CoreValue;
@@ -74,6 +77,7 @@ public class Query {
     private boolean explain;
     private long limit = Long.MAX_VALUE;
     private long offset;
+    private long size = -1;
     private boolean prepared;
     private final CoreValueFactory valueFactory;
     private ContentSession session;
@@ -146,7 +150,7 @@ public class Query {
             public boolean visit(FullTextSearchImpl node) {
                 node.setQuery(query);
                 node.bindSelector(source);
-                return true;
+                return super.visit(node);
             }
 
             @Override
@@ -309,6 +313,7 @@ public class Query {
                     ResultRowImpl r = it.next();
                     list.add(r);
                 }
+                size = list.size();
                 Collections.sort(list);
                 it = list.iterator();
             }
@@ -316,22 +321,22 @@ public class Query {
         return it;
     }
 
-    public int compareRows(CoreValue[] orderValues, CoreValue[] orderValues2) {
+    public int compareRows(CoreValue[][] orderValues, CoreValue[][] orderValues2) {
         int comp = 0;
         for (int i = 0, size = orderings.length; i < size; i++) {
-            CoreValue a = orderValues[i];
-            CoreValue b = orderValues2[i];
+            CoreValue[] a = orderValues[i];
+            CoreValue[] b = orderValues2[i];
             if (a == null || b == null) {
                 if (a == b) {
                     comp = 0;
                 } else if (a == null) {
-                    // TODO order by: nulls first, last, low or high?
-                    comp = 1;
-                } else {
+                    // TODO order by: nulls first (it looks like), or low?
                     comp = -1;
+                } else {
+                    comp = 1;
                 }
             } else {
-                comp = a.compareTo(b);
+                comp = compareValues(a, b);
             }
             if (comp != 0) {
                 if (orderings[i].isDescending()) {
@@ -343,6 +348,112 @@ public class Query {
         return comp;
     }
 
+    public static int compareValues(CoreValue[] orderValues, CoreValue[] orderValues2) {
+        int l1 = orderValues.length;
+        int l2 = orderValues2.length;
+        int len = Math.max(l1, l2);
+        for (int i = 0; i < len; i++) {
+            CoreValue a = i < l1 ? orderValues[i] : null;
+            CoreValue b = i < l2 ? orderValues2[i] : null;
+            int comp;
+            if (a == null) {
+                comp = 1;
+            } else if (b == null) {
+                comp = -1;
+            } else {
+                comp = a.compareTo(b);
+            }
+            if (comp != 0) {
+                return comp;
+            }
+        }
+        return 0;
+    }
+
+    public static int getType(PropertyState p, int ifUnknown) {
+        if (!p.isArray()) {
+            return p.getValue().getType();
+        }
+        Iterator<CoreValue> it = p.getValues().iterator();
+        if (it.hasNext()) {
+            return it.next().getType();
+        }
+        return ifUnknown;
+    }
+
+    public CoreValue convert(CoreValue v, int targetType) {
+        // TODO support full set of conversion features defined in the JCR spec
+        // at 3.6.4 Property Type Conversion
+        // re-use existing code if possible
+        int sourceType = v.getType();
+        if (sourceType == targetType) {
+            return v;
+        }
+        CoreValueFactory vf = getValueFactory();
+        switch (sourceType) {
+        case PropertyType.STRING:
+            switch(targetType) {
+            case PropertyType.BINARY:
+                try {
+                    byte[] data = v.getString().getBytes("UTF-8");
+                    return vf.createValue(new ByteArrayInputStream(data));
+                } catch (IOException e) {
+                    // I don't know in what case that could really occur
+                    // except if UTF-8 isn't supported
+                    throw new IllegalArgumentException(v.getString(), e);
+                }
+            }
+            return vf.createValue(v.getString(), targetType);
+        }
+        switch (targetType) {
+        case PropertyType.STRING:
+            return vf.createValue(v.getString());
+        case PropertyType.BOOLEAN:
+            return vf.createValue(v.getBoolean());
+        case PropertyType.DATE:
+            return vf.createValue(v.getString(), PropertyType.DATE);
+        case PropertyType.LONG:
+            return vf.createValue(v.getLong());
+        case PropertyType.DOUBLE:
+            return vf.createValue(v.getDouble());
+        case PropertyType.DECIMAL:
+            return vf.createValue(v.getString(), PropertyType.DECIMAL);
+        case PropertyType.NAME:
+            return vf.createValue(getOakPath(v.getString()), PropertyType.NAME);
+        case PropertyType.PATH:
+            return vf.createValue(v.getString(), PropertyType.PATH);
+        case PropertyType.REFERENCE:
+            return vf.createValue(v.getString(), PropertyType.REFERENCE);
+        case PropertyType.WEAKREFERENCE:
+            return vf.createValue(v.getString(), PropertyType.WEAKREFERENCE);
+        case PropertyType.URI:
+            return vf.createValue(v.getString(), PropertyType.URI);
+        case PropertyType.BINARY:
+            try {
+                byte[] data = v.getString().getBytes("UTF-8");
+                return vf.createValue(new ByteArrayInputStream(data));
+            } catch (IOException e) {
+                // I don't know in what case that could really occur
+                // except if UTF-8 isn't supported
+                throw new IllegalArgumentException(v.getString(), e);
+            }
+        }
+        throw new IllegalArgumentException("Unknown property type: " + targetType);
+    }
+
+    public String getOakPath(String jcrPath) {
+        NamePathMapper m = getNamePathMapper();
+        if (m == null) {
+            // to simplify testing, a getNamePathMapper isn't required
+            return jcrPath;
+        }
+        String p = m.getOakPath(jcrPath);
+        if (p == null) {
+            throw new IllegalArgumentException("Not a valid JCR path: " + jcrPath);
+        }
+        return p;
+    }
+
     void prepare() {
         if (prepared) {
             return;
@@ -444,17 +555,24 @@ public class Query {
             PropertyState p = c.currentProperty();
             values[i] = p == null ? null : p.getValue();
         }
-        CoreValue[] orderValues;
+        CoreValue[][] orderValues;
         if (orderings == null) {
             orderValues = null;
         } else {
             int size = orderings.length;
-            orderValues = new CoreValue[size];
+            orderValues = new CoreValue[size][];
             for (int i = 0; i < size; i++) {
                 PropertyState p = orderings[i].getOperand().currentProperty();
-                // TODO how is order by multi-values properties defined?
-                // currently throws an exception
-                orderValues[i] = p == null ? null : p.getValue();
+                CoreValue[] x;
+                if (p == null) {
+                    x = null;
+                } else if (p.isArray()) {
+                    List<CoreValue> list = p.getValues();
+                    x = list.toArray(new CoreValue[list.size()]);
+                } else {
+                    x = new CoreValue[] { p.getValue() };
+                }
+                orderValues[i] = x;
             }
         }
         return new ResultRowImpl(this, paths, values, orderValues);
@@ -550,4 +668,8 @@ public class Query {
         return buff.toString();
     }
 
+    public long getSize() {
+        return size;
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ResultImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ResultImpl.java?rev=1378867&r1=1378866&r2=1378867&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ResultImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ResultImpl.java Thu Aug 30 09:20:10 2012
@@ -67,7 +67,13 @@ public class ResultImpl implements Resul
             public Iterator<ResultRowImpl> iterator() {
                 return query.getRows(revisionId, root);
             }
+
         };
     }
 
+    @Override
+    public long getSize() {
+        return query.getSize();
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ResultRowImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ResultRowImpl.java?rev=1378867&r1=1378866&r2=1378867&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ResultRowImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ResultRowImpl.java Thu Aug 30 09:20:10 2012
@@ -29,9 +29,9 @@ public class ResultRowImpl implements Re
     private final Query query;
     private final String[] paths;
     private final CoreValue[] values;
-    private final CoreValue[] orderValues;
+    private final CoreValue[][] orderValues;
 
-    ResultRowImpl(Query query, String[] paths, CoreValue[] values, CoreValue[] orderValues) {
+    ResultRowImpl(Query query, String[] paths, CoreValue[] values, CoreValue[][] orderValues) {
         this.query = query;
         this.paths = paths;
         this.values = values;

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SQL2Parser.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SQL2Parser.java?rev=1378867&r1=1378866&r2=1378867&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SQL2Parser.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SQL2Parser.java Thu Aug 30 09:20:10 2012
@@ -1029,6 +1029,7 @@ public class SQL2Parser {
                 // (not in the JCR 1.0 spec)
                 // (confusing isn't it?)
                 currentTokenType = IDENTIFIER;
+                currentToken = currentValue.getString();
             }
             return;
         case CHAR_END:

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/XPathToSQL2Converter.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/XPathToSQL2Converter.java?rev=1378867&r1=1378866&r2=1378867&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/XPathToSQL2Converter.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/XPathToSQL2Converter.java Thu Aug 30 09:20:10 2012
@@ -441,7 +441,11 @@ public class XPathToSQL2Converter {
             read(")");
             return f;
          } else if ("jcr:deref".equals(functionName)) {
+             // TODO support jcr:deref
              throw getSyntaxError("jcr:deref is not supported");
+         } else if ("rep:similar".equals(functionName)) {
+             // TODO support rep:similar
+             throw getSyntaxError("rep:similar is not supported");
         } else {
             throw getSyntaxError("jcr:like | jcr:contains | jcr:score | jcr:deref | fn:lower-case | fn:upper-case");
         }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElement.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElement.java?rev=1378867&r1=1378866&r2=1378867&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElement.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElement.java Thu Aug 30 09:20:10 2012
@@ -18,17 +18,9 @@
  */
 package org.apache.jackrabbit.oak.query.ast;
 
-import org.apache.jackrabbit.mk.json.JsopReader;
-import org.apache.jackrabbit.mk.json.JsopTokenizer;
-import org.apache.jackrabbit.oak.api.CoreValue;
-import org.apache.jackrabbit.oak.api.CoreValueFactory;
 import org.apache.jackrabbit.oak.api.Tree;
-import org.apache.jackrabbit.oak.kernel.CoreValueMapper;
-import org.apache.jackrabbit.oak.namepath.NamePathMapper;
 import org.apache.jackrabbit.oak.query.Query;
 
-import javax.jcr.PropertyType;
-
 /**
  * The base class for all abstract syntax tree nodes.
  */
@@ -76,59 +68,6 @@ abstract class AstElement {
         return path;
     }
 
-    /**
-     * Convert the JSON property value to a core value.
-     *
-     * @param propertyValue JSON property value
-     * @return the core value
-     */
-    protected CoreValue getCoreValue(String propertyValue) {
-        // TODO data type mapping
-        CoreValueFactory vf = query.getValueFactory();
-        JsopReader r = new JsopTokenizer(propertyValue);
-        if (r.matches('[')) {
-            // TODO support arrays, but only for comparisons
-            throw new IllegalArgumentException("Arrays are currently not supported: " + propertyValue);
-        }
-        return CoreValueMapper.fromJsopReader(r, vf);
-    }
-
-    /**
-     * Validate that the given value can be converted to a JCR name.
-     *
-     * @param v the value
-     * @return true if it can be converted
-     */
-    protected boolean isName(CoreValue v) {
-        // TODO correctly validate JCR names - see JCR 2.0 spec 3.2.4 Naming Restrictions
-        switch (v.getType()) {
-        case PropertyType.DATE:
-        case PropertyType.DECIMAL:
-        case PropertyType.DOUBLE:
-        case PropertyType.LONG:
-        case PropertyType.BOOLEAN:
-            return false;
-        }
-        String n = v.getString();
-        if (n.startsWith("[") && !n.endsWith("]")) {
-            return false;
-        }
-        return true;
-    }
-
-    protected String getOakPath(String jcrPath) {
-        NamePathMapper m = query.getNamePathMapper();
-        if (m == null) {
-            // to simplify testing, a getNamePathMapper isn't required
-            return jcrPath;
-        }
-        String p = m.getOakPath(jcrPath);
-        if (p == null) {
-            throw new IllegalArgumentException("Not a valid JCR path: " + jcrPath);
-        }
-        return p;
-    }
-
     protected Tree getTree(String path) {
         return query.getTree(path);
     }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ComparisonImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ComparisonImpl.java?rev=1378867&r1=1378866&r2=1378867&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ComparisonImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ComparisonImpl.java Thu Aug 30 09:20:10 2012
@@ -18,13 +18,10 @@
  */
 package org.apache.jackrabbit.oak.query.ast;
 
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.util.Iterator;
-import javax.jcr.PropertyType;
 import org.apache.jackrabbit.oak.api.CoreValue;
 import org.apache.jackrabbit.oak.api.CoreValueFactory;
 import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.query.Query;
 import org.apache.jackrabbit.oak.query.index.FilterImpl;
 
 /**
@@ -69,22 +66,11 @@ public class ComparisonImpl extends Cons
             return false;
         }
         boolean isArray = p1.isArray();
-        int v1Type;
-        if (isArray) {
-            Iterator<CoreValue> it = p1.getValues().iterator();
-            if (it.hasNext()) {
-                v1Type = it.next().getType();
-            } else {
-                // workaround for OAK-140: PropertyState: data type of empty array property
-                v1Type = v2.getType();
-            }
-        } else {
-            v1Type = p1.getValue().getType();
-        }
+        int v1Type = Query.getType(p1, v2.getType());
         if (v1Type != v2.getType()) {
             // "the value of operand2 is converted to the
             // property type of the value of operand1"
-            v2 = convert(v2, v1Type);
+            v2 = query.convert(v2, v1Type);
         }
         if (!isArray) {
             return evaluate(p1.getValue(), v2);
@@ -119,66 +105,6 @@ public class ComparisonImpl extends Cons
         throw new IllegalArgumentException("Unknown operator: " + operator);
     }
 
-    private CoreValue convert(CoreValue v, int targetType) {
-        // TODO support full set of conversion features defined in the JCR spec
-        // at 3.6.4 Property Type Conversion
-        // re-use existing code if possible
-        int sourceType = v.getType();
-        if (sourceType == targetType) {
-            return v;
-        }
-        CoreValueFactory vf = query.getValueFactory();
-        switch (sourceType) {
-        case PropertyType.STRING:
-            switch(targetType) {
-            case PropertyType.BINARY:
-                try {
-                    byte[] data = v.getString().getBytes("UTF-8");
-                    return vf.createValue(new ByteArrayInputStream(data));
-                } catch (IOException e) {
-                    // I don't know in what case that could really occur
-                    // except if UTF-8 isn't supported
-                    throw new IllegalArgumentException(v.getString(), e);
-                }
-            }
-            return vf.createValue(v.getString(), targetType);
-        }
-        switch (targetType) {
-        case PropertyType.STRING:
-            return vf.createValue(v.getString());
-        case PropertyType.BOOLEAN:
-            return vf.createValue(v.getBoolean());
-        case PropertyType.DATE:
-            return vf.createValue(v.getString(), PropertyType.DATE);
-        case PropertyType.LONG:
-            return vf.createValue(v.getLong());
-        case PropertyType.DOUBLE:
-            return vf.createValue(v.getDouble());
-        case PropertyType.DECIMAL:
-            return vf.createValue(v.getString(), PropertyType.DECIMAL);
-        case PropertyType.NAME:
-            return vf.createValue(getOakPath(v.getString()), PropertyType.NAME);
-        case PropertyType.PATH:
-            return vf.createValue(v.getString(), PropertyType.PATH);
-        case PropertyType.REFERENCE:
-            return vf.createValue(v.getString(), PropertyType.REFERENCE);
-        case PropertyType.WEAKREFERENCE:
-            return vf.createValue(v.getString(), PropertyType.WEAKREFERENCE);
-        case PropertyType.URI:
-            return vf.createValue(v.getString(), PropertyType.URI);
-        case PropertyType.BINARY:
-            try {
-                byte[] data = v.getString().getBytes("UTF-8");
-                return vf.createValue(new ByteArrayInputStream(data));
-            } catch (IOException e) {
-                // I don't know in what case that could really occur
-                // except if UTF-8 isn't supported
-                throw new IllegalArgumentException(v.getString(), e);
-            }
-        }
-        throw new IllegalArgumentException("Unknown property type: " + targetType);
-    }
-
     private static boolean evaluateLike(CoreValue v1, CoreValue v2) {
         LikePattern like = new LikePattern(v2.getString());
         return like.matches(v1.getString());
@@ -206,7 +132,7 @@ public class ComparisonImpl extends Cons
                 String upperBound = p.getUpperBound();
                 if (lowerBound == null && upperBound == null) {
                     // ignore
-                } else {
+                } else if (operand1.supportsRangeConditions()) {
                     CoreValueFactory vf = query.getValueFactory();
                     if (lowerBound != null) {
                         operand1.apply(f, Operator.GREATER_OR_EQUAL, vf.createValue(lowerBound));

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DynamicOperandImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DynamicOperandImpl.java?rev=1378867&r1=1378866&r2=1378867&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DynamicOperandImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DynamicOperandImpl.java Thu Aug 30 09:20:10 2012
@@ -29,4 +29,8 @@ public abstract class DynamicOperandImpl
 
     public abstract void apply(FilterImpl f, Operator operator, CoreValue v);
 
+    public boolean supportsRangeConditions() {
+        return true;
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/EquiJoinConditionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/EquiJoinConditionImpl.java?rev=1378867&r1=1378866&r2=1378867&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/EquiJoinConditionImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/EquiJoinConditionImpl.java Thu Aug 30 09:20:10 2012
@@ -18,7 +18,9 @@
  */
 package org.apache.jackrabbit.oak.query.ast;
 
+import org.apache.jackrabbit.oak.api.CoreValue;
 import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.query.Query;
 import org.apache.jackrabbit.oak.query.index.FilterImpl;
 
 /**
@@ -89,27 +91,49 @@ public class EquiJoinConditionImpl exten
             return p1.getValue().equals(p2.getValue());
         }
         // TODO what is the expected result of an equi join for multi-valued properties?
-        throw new IllegalArgumentException("Join on multi-valued properties is currently not supported");
+        if (!p1.isArray() && p2.isArray()) {
+            CoreValue x = p1.getValue();
+            for (CoreValue y : p2.getValues()) {
+                if (y.getType() != x.getType()) {
+                    y = query.convert(y, x.getType());
+                }
+                if (x.equals(y)) {
+                    return true;
+                }
+            }
+            return false;
+        } else if (p1.isArray() && !p2.isArray()) {
+            CoreValue x = p2.getValue();
+            for (CoreValue y : p1.getValues()) {
+                if (y.getType() != x.getType()) {
+                    y = query.convert(y, x.getType());
+                }
+                if (x.equals(y)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        CoreValue[] l1 = p1.getValues().toArray(new CoreValue[p1.getValues().size()]);
+        CoreValue[] l2 = p2.getValues().toArray(new CoreValue[p2.getValues().size()]);
+        return Query.compareValues(l1, l2) == 0;
     }
 
     @Override
     public void apply(FilterImpl f) {
-        // TODO what is the expected result of an equi join for multi-valued properties?
         PropertyState p1 = selector1.currentProperty(property1Name);
         PropertyState p2 = selector2.currentProperty(property2Name);
         if (f.getSelector() == selector1 && p2 != null) {
-            if (p2.isArray()) {
-                // TODO what is the expected result of an equi join for multi-valued properties?
-                throw new IllegalArgumentException("Join on multi-valued properties is currently not supported");
+            if (!p2.isArray()) {
+                // TODO support join on multi-valued properties
+                f.restrictProperty(property1Name, Operator.EQUAL, p2.getValue());
             }
-            f.restrictProperty(property1Name, Operator.EQUAL, p2.getValue());
         }
         if (f.getSelector() == selector2 && p1 != null) {
-            if (p1.isArray()) {
-                // TODO what is the expected result of an equi join for multi-valued properties?
-                throw new IllegalArgumentException("Join on multi-valued properties is currently not supported");
+            if (!p1.isArray()) {
+                // TODO support join on multi-valued properties
+                f.restrictProperty(property2Name, Operator.EQUAL, p1.getValue());
             }
-            f.restrictProperty(property2Name, Operator.EQUAL, p1.getValue());
         }
     }
 

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java?rev=1378867&r1=1378866&r2=1378867&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java Thu Aug 30 09:20:10 2012
@@ -23,6 +23,7 @@ import java.util.ArrayList;
 import org.apache.jackrabbit.oak.api.CoreValue;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.query.ast.ComparisonImpl.LikePattern;
 import org.apache.jackrabbit.oak.query.index.FilterImpl;
 
 /**
@@ -34,7 +35,6 @@ public class FullTextSearchImpl extends 
     private final String propertyName;
     private final StaticOperandImpl fullTextSearchExpression;
     private SelectorImpl selector;
-    private FullTextExpression expr;
 
     public FullTextSearchImpl(String selectorName, String propertyName,
             StaticOperandImpl fullTextSearchExpression) {
@@ -77,48 +77,51 @@ public class FullTextSearchImpl extends 
         return builder.toString();
     }
 
-    private boolean evaluateContains(PropertyState p) {
-        for (CoreValue v : p.getValues()) {
-            if (evaluateContains(v)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private boolean evaluateContains(CoreValue value) {
-        String v = value.getString();
-        return expr.evaluate(v);
-    }
-
     @Override
     public boolean evaluate() {
+        StringBuilder buff = new StringBuilder();
         if (propertyName != null) {
             PropertyState p = selector.currentProperty(propertyName);
             if (p == null) {
                 return false;
             }
-            return evaluateContains(p);
-        }
-        Tree tree = getTree(selector.currentPath());
-        for (PropertyState p : tree.getProperties()) {
-            if (evaluateContains(p)) {
-                return true;
+            appendString(buff, p);
+        } else {
+            Tree tree = getTree(selector.currentPath());
+            if (tree == null) {
+                return false;
+            }
+            for (PropertyState p : tree.getProperties()) {
+                appendString(buff, p);
             }
         }
-        return false;
-    }
-
-    public void bindSelector(SourceImpl source) {
-        selector = source.getExistingSelector(selectorName);
+        // TODO fulltext conditions: need a way to disable evaluation
+        // if a fulltext index is used, to avoid filtering too much
+        // (we don't know what exact options are used in the fulltext index)
+        // (stop word, special characters,...)
         CoreValue v = fullTextSearchExpression.currentValue();
         try {
-            expr = FullTextParser.parse(v.getString());
+            FullTextExpression expr = FullTextParser.parse(v.getString());
+            return expr.evaluate(buff.toString());
         } catch (ParseException e) {
             throw new IllegalArgumentException("Invalid expression: " + fullTextSearchExpression, e);
         }
     }
 
+    private static void appendString(StringBuilder buff, PropertyState p) {
+        if (p.isArray()) {
+            for (CoreValue v : p.getValues()) {
+                buff.append(v.getString()).append(' ');
+            }
+        } else {
+            buff.append(p.getValue().getString()).append(' ');
+        }
+    }
+
+    public void bindSelector(SourceImpl source) {
+        selector = source.getExistingSelector(selectorName);
+    }
+
     @Override
     public void apply(FilterImpl f) {
         if (propertyName != null) {
@@ -183,15 +186,16 @@ public class FullTextSearchImpl extends 
             if (parseIndex >= text.length()) {
                 throw getSyntaxError("term");
             }
-            FullTextTerm term = new FullTextTerm();
+            boolean not = false;
             StringBuilder buff = new StringBuilder();
             char c = text.charAt(parseIndex);
             if (c == '-') {
                 if (++parseIndex >= text.length()) {
                     throw getSyntaxError("term");
                 }
-                term.not = true;
+                not = true;
             }
+            boolean escaped = false;
             if (c == '\"') {
                 parseIndex++;
                 while (true) {
@@ -200,7 +204,7 @@ public class FullTextSearchImpl extends 
                     }
                     c = text.charAt(parseIndex++);
                     if (c == '\\') {
-                        // escape
+                        escaped = true;
                         if (parseIndex >= text.length()) {
                             throw getSyntaxError("escaped char");
                         }
@@ -220,7 +224,7 @@ public class FullTextSearchImpl extends 
                 do {
                     c = text.charAt(parseIndex++);
                     if (c == '\\') {
-                        // escape
+                        escaped = true;
                         if (parseIndex >= text.length()) {
                             throw getSyntaxError("escaped char");
                         }
@@ -236,7 +240,8 @@ public class FullTextSearchImpl extends 
             if (buff.length() == 0) {
                 throw getSyntaxError("term");
             }
-            term.text = buff.toString();
+            String text = buff.toString();
+            FullTextTerm term = new FullTextTerm(text, not, escaped);
             return term.simplify();
         }
 
@@ -335,15 +340,62 @@ public class FullTextSearchImpl extends 
      * A fulltext term, or a "not" term.
      */
     static class FullTextTerm extends FullTextExpression {
-        boolean not;
-        String text;
+        private final boolean not;
+        private final String text;
+        private final String filteredText;
+        private final LikePattern like;
+
+        FullTextTerm(String text, boolean not, boolean escaped) {
+            this.text = text;
+            this.not = not;
+            // for testFulltextIntercapSQL
+            // filter special characters such as '
+            // to make tests pass, for example the
+            // FulltextQueryTest.testFulltextExcludeSQL,
+            // which searches for:
+            // "text ''fox jumps'' -other"
+            // (please note the two single quotes instead of
+            // double quotes before for and after jumps)
+            boolean pattern = false;
+            if (escaped) {
+                filteredText = text;
+            } else {
+                StringBuilder buff = new StringBuilder();
+                for (int i = 0; i < text.length(); i++) {
+                    char c = text.charAt(i);
+                    if (c == '*') {
+                        buff.append('%');
+                        pattern = true;
+                    } else if (c == '?') {
+                        buff.append('_');
+                        pattern = true;
+                    } else if (c == '_') {
+                        buff.append("\\_");
+                        pattern = true;
+                    } else if (Character.isLetterOrDigit(c) || " +-:&".indexOf(c) >= 0) {
+                        buff.append(c);
+                    }
+                }
+                this.filteredText = buff.toString().toLowerCase();
+            }
+            if (pattern) {
+                like = new LikePattern("%" + filteredText + "%");
+            } else {
+                like = null;
+            }
+        }
 
         @Override
         public boolean evaluate(String value) {
+            // for testFulltextIntercapSQL
+            value = value.toLowerCase();
+            if (like != null) {
+                return like.matches(value);
+            }
             if (not) {
-                return value.indexOf(text) < 0;
+                return value.indexOf(filteredText) < 0;
             }
-            return value.indexOf(text) >= 0;
+            return value.indexOf(filteredText) >= 0;
         }
 
         @Override

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NodeLocalNameImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NodeLocalNameImpl.java?rev=1378867&r1=1378866&r2=1378867&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NodeLocalNameImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NodeLocalNameImpl.java Thu Aug 30 09:20:10 2012
@@ -23,6 +23,7 @@ import org.apache.jackrabbit.oak.api.Cor
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.plugins.memory.SinglePropertyState;
 import org.apache.jackrabbit.oak.query.index.FilterImpl;
+import org.apache.jackrabbit.util.ISO9075;
 
 /**
  * The function "localname(..)".
@@ -57,6 +58,8 @@ public class NodeLocalNameImpl extends D
     @Override
     public PropertyState currentProperty() {
         String name = PathUtils.getName(selector.currentPath());
+        // Name escaping (convert space to _x0020_)
+        name = ISO9075.encode(name);
         int colon = name.indexOf(':');
         // TODO LOCALNAME: evaluation of local name might not be correct
         String localName = colon < 0 ? name : name.substring(colon + 1);

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NodeNameImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NodeNameImpl.java?rev=1378867&r1=1378866&r2=1378867&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NodeNameImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NodeNameImpl.java Thu Aug 30 09:20:10 2012
@@ -24,6 +24,7 @@ import org.apache.jackrabbit.oak.api.Pro
 import org.apache.jackrabbit.oak.commons.PathUtils;
 import org.apache.jackrabbit.oak.plugins.memory.SinglePropertyState;
 import org.apache.jackrabbit.oak.query.index.FilterImpl;
+import org.apache.jackrabbit.util.ISO9075;
 
 /**
  * The function "name(..)".
@@ -56,13 +57,16 @@ public class NodeNameImpl extends Dynami
     }
 
     @Override
+    public boolean supportsRangeConditions() {
+        return false;
+    }
+
+    @Override
     public PropertyState currentProperty() {
-        String name = PathUtils.getName(selector.currentPath());
-        CoreValue v = query.getValueFactory().createValue(name);
-        String path = v.getString();
-        // normalize paths (./name > name)
-        path = getOakPath(path);
-        CoreValue v2 = query.getValueFactory().createValue(path, PropertyType.NAME);
+        String path = selector.currentPath();
+        // Name escaping (convert space to _x0020_)
+        String name = ISO9075.encode(PathUtils.getName(path));
+        CoreValue v2 = query.getValueFactory().createValue(name, PropertyType.NAME);
         return new SinglePropertyState("NAME", v2);
     }
 
@@ -72,8 +76,8 @@ public class NodeNameImpl extends Dynami
             throw new IllegalArgumentException("Invalid name value: " + v.toString());
         }
         String path = v.getString();
-        // normalize paths (./name > name)
-        path = getOakPath(path);
+        // Name escaping (convert _x0020_ to space)
+        path = decodeName(path);
         if (PathUtils.isAbsolute(path)) {
             throw new IllegalArgumentException("NAME() comparison with absolute path are not allowed: " + path);
         }
@@ -83,4 +87,35 @@ public class NodeNameImpl extends Dynami
         // TODO support NAME(..) index conditions
     }
 
+    private String decodeName(String path) {
+        // Name escaping (convert _x0020_ to space)
+        path = ISO9075.decode(path);
+        // normalize paths (./name > name)
+        path = query.getOakPath(path);
+        return path;
+    }
+
+    /**
+     * Validate that the given value can be converted to a JCR name.
+     *
+     * @param v the value
+     * @return true if it can be converted
+     */
+    private static boolean isName(CoreValue v) {
+        // TODO correctly validate JCR names - see JCR 2.0 spec 3.2.4 Naming Restrictions
+        switch (v.getType()) {
+        case PropertyType.DATE:
+        case PropertyType.DECIMAL:
+        case PropertyType.DOUBLE:
+        case PropertyType.LONG:
+        case PropertyType.BOOLEAN:
+            return false;
+        }
+        String n = v.getString();
+        if (n.startsWith("[") && !n.endsWith("]")) {
+            return false;
+        }
+        return true;
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java?rev=1378867&r1=1378866&r2=1378867&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java Thu Aug 30 09:20:10 2012
@@ -86,6 +86,9 @@ public class PropertyValueImpl extends D
             return matchesPropertyType(p) ? p : null;
         }
         Tree tree = getTree(selector.currentPath());
+        if (tree == null) {
+            return null;
+        }
         if (relative) {
             for (String p : PathUtils.elements(PathUtils.getParentPath(propertyName))) {
                 if (tree == null) {

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java?rev=1378867&r1=1378866&r2=1378867&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java Thu Aug 30 09:20:10 2012
@@ -119,6 +119,9 @@ public class SelectorImpl extends Source
                 return true;
             }
             Tree tree = getTree(cursor.currentRow().getPath());
+            if (tree == null) {
+                return false;
+            }
             PropertyState p = tree.getProperty(JCR_PRIMARY_TYPE);
             if (p == null) {
                 return true;
@@ -171,7 +174,8 @@ public class SelectorImpl extends Source
         if (path == null) {
             return null;
         }
-        return getTree(path).getProperty(propertyName);
+        Tree t = getTree(path);
+        return t == null ? null : t.getProperty(propertyName);
     }
 
     @Override

Modified: jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/sql2.txt
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/sql2.txt?rev=1378867&r1=1378866&r2=1378867&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/sql2.txt (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/sql2.txt Thu Aug 30 09:20:10 2012
@@ -77,6 +77,13 @@ commit / - "test"
 
 # other tests
 
+commit / + "test": { "My Documents": { "x" : {}}}
+
+select [jcr:path] from [nt:base] where name() = 'My_x0020_Documents'
+/test/My Documents
+
+commit / - "test"
+
 commit / + "test": { "jcr:resource": {}, "resource": { "x" : {}}}
 
 select * from [nt:base] where id = -1
@@ -242,11 +249,6 @@ commit / + "test4": { "name": "10%" }
 commit / + "test5": { "name": "10 percent" }
 
 select name from [nt:base] order by upper(name)
-10 percent
-10%
-Hallo
-hello
-World!
 null
 null
 null
@@ -256,6 +258,11 @@ null
 null
 null
 null
+10 percent
+10%
+Hallo
+hello
+World!
 
 select * from [nt:base] where length(name) = 5
 /test

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/QueryResultImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/QueryResultImpl.java?rev=1378867&r1=1378866&r2=1378867&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/QueryResultImpl.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/QueryResultImpl.java Thu Aug 30 09:20:10 2012
@@ -123,7 +123,11 @@ public class QueryResultImpl implements 
             }
 
         };
-        return new RowIteratorAdapter(rowIterator);
+        return new RowIteratorAdapter(rowIterator) {
+            public long getSize() {
+                return result.getSize();
+            }
+        };
     }
 
     @CheckForNull
@@ -184,11 +188,11 @@ public class QueryResultImpl implements 
             }
 
         };
-        return new NodeIteratorAdapter(nodeIterator);
+        return new NodeIteratorAdapter(nodeIterator, result.getSize());
     }
 
     Value createValue(CoreValue value) {
-        return sessionDelegate.getValueFactory().createValue(value);
+        return value == null ? null : sessionDelegate.getValueFactory().createValue(value);
     }
 
 }

Modified: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java?rev=1378867&r1=1378866&r2=1378867&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java Thu Aug 30 09:20:10 2012
@@ -18,6 +18,10 @@
  */
 package org.apache.jackrabbit.oak.jcr.query;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import java.util.NoSuchElementException;
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
 import javax.jcr.RepositoryException;
@@ -28,14 +32,9 @@ import javax.jcr.query.QueryManager;
 import javax.jcr.query.QueryResult;
 import javax.jcr.query.Row;
 import javax.jcr.query.RowIterator;
-
 import org.apache.jackrabbit.oak.jcr.AbstractRepositoryTest;
 import org.junit.Test;
 
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
-import static org.junit.Assert.assertFalse;
-
 /**
  * Tests the query feature.
  */
@@ -83,4 +82,46 @@ public class QueryTest extends AbstractR
         q.execute();
     }
 
+    @Test
+    public void skip() throws RepositoryException {
+        Session session = getAdminSession();
+        Node hello1 = session.getRootNode().addNode("hello1");
+        hello1.setProperty("id",  "1");
+        hello1.setProperty("data",  "x");
+        Node hello2 = session.getRootNode().addNode("hello2");
+        hello2.setProperty("id",  "2");
+        hello2.setProperty("data",  "y");
+        session.save();
+        ValueFactory vf = session.getValueFactory();
+        QueryManager qm = session.getWorkspace().getQueryManager();
+        Query q = qm.createQuery("select id from [nt:base] where data >= $data order by id", Query.JCR_SQL2);
+        q.bindValue("data", vf.createValue("x"));
+        for (int i = -1; i < 4; i++) {
+            QueryResult r = q.execute();
+            RowIterator it = r.getRows();
+            assertEquals(2, r.getRows().getSize());
+            assertEquals(2, r.getNodes().getSize());
+            Row row;
+            try {
+                it.skip(i);
+                assertTrue(i >= 0 && i <= 2);
+            } catch (IllegalArgumentException e) {
+                assertEquals(-1, i);
+            } catch (NoSuchElementException e) {
+                assertTrue(i >= 2);
+            }
+            if (i <= 0) {
+                assertTrue(it.hasNext());
+                row = it.nextRow();
+                assertEquals("1", row.getValue("id").getString());
+            }
+            if (i <= 1) {
+                assertTrue(it.hasNext());
+                row = it.nextRow();
+                assertEquals("2", row.getValue("id").getString());
+            }
+            assertFalse(it.hasNext());
+        }
+    }
+
 }