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 2014/04/01 10:04:49 UTC

svn commit: r1583563 - in /jackrabbit/oak/trunk: oak-core/src/main/java/org/apache/jackrabbit/oak/query/ oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/ oak-lucene/src/main/java/...

Author: thomasm
Date: Tue Apr  1 08:04:49 2014
New Revision: 1583563

URL: http://svn.apache.org/r1583563
Log:
OAK-319 Similar (rep:similar) support

Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SimilarImpl.java
Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.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/ast/AstElementFactory.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstVisitor.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstVisitorBase.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/Expression.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/XPathToSQL2Converter.java
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldFactory.java
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/MoreLikeThisHelper.java
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexQueryTest.java

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java?rev=1583563&r1=1583562&r2=1583563&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java Tue Apr  1 08:04:49 2014
@@ -60,6 +60,7 @@ import org.apache.jackrabbit.oak.query.a
 import org.apache.jackrabbit.oak.query.ast.SameNodeImpl;
 import org.apache.jackrabbit.oak.query.ast.SameNodeJoinConditionImpl;
 import org.apache.jackrabbit.oak.query.ast.SelectorImpl;
+import org.apache.jackrabbit.oak.query.ast.SimilarImpl;
 import org.apache.jackrabbit.oak.query.ast.SourceImpl;
 import org.apache.jackrabbit.oak.query.ast.UpperCaseImpl;
 import org.apache.jackrabbit.oak.query.index.FilterImpl;
@@ -209,6 +210,13 @@ public class QueryImpl implements Query 
                 node.bindSelector(source);
                 return super.visit(node);
             }
+            
+            @Override
+            public boolean visit(SimilarImpl node) {
+                node.setQuery(query);
+                node.bindSelector(source);
+                return super.visit(node);
+            }
 
             @Override
             public boolean visit(FullTextSearchScoreImpl node) {

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=1583563&r1=1583562&r2=1583563&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 Tue Apr  1 08:04:49 2014
@@ -516,6 +516,32 @@ public class SQL2Parser {
             } else {
                 c = factory.descendantNode(getOnlySelectorName(), name);
             }
+        } else if ("SIMILAR".equalsIgnoreCase(functionName)) {
+            if (readIf(".") || readIf("*")) {
+                read(",");
+                c = factory.similar(
+                        getOnlySelectorName(), null, parseStaticOperand());
+            } else {
+                String name = readName();
+                if (readIf(".")) {
+                    if (readIf("*")) {
+                        read(",");
+                        c = factory.fullTextSearch(
+                                name, null, parseStaticOperand());
+                    } else {
+                        String selector = name;
+                        name = readName();
+                        read(",");
+                        c = factory.fullTextSearch(
+                                selector, name, parseStaticOperand());
+                    }
+                } else {
+                    read(",");
+                    c = factory.fullTextSearch(
+                            getOnlySelectorName(), name,
+                            parseStaticOperand());
+                }
+            }
         } else if ("NATIVE".equalsIgnoreCase(functionName)) {
             String selectorName;
             if (currentTokenType == IDENTIFIER) {

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElementFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElementFactory.java?rev=1583563&r1=1583562&r2=1583563&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElementFactory.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElementFactory.java Tue Apr  1 08:04:49 2014
@@ -152,4 +152,9 @@ public class AstElementFactory {
         return new NativeFunctionImpl(selectorName, language, expression);
     }
 
+    public SimilarImpl similar(String selectorName, String propertyName,
+            StaticOperandImpl path) {
+        return new SimilarImpl(selectorName, propertyName, path);
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstVisitor.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstVisitor.java?rev=1583563&r1=1583562&r2=1583563&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstVisitor.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstVisitor.java Tue Apr  1 08:04:49 2014
@@ -81,4 +81,6 @@ public interface AstVisitor {
 
     boolean visit(NativeFunctionImpl nativeFunctionImpl);
 
+    boolean visit(SimilarImpl similarImpl);
+
 }
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstVisitorBase.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstVisitorBase.java?rev=1583563&r1=1583562&r2=1583563&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstVisitorBase.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstVisitorBase.java Tue Apr  1 08:04:49 2014
@@ -69,6 +69,16 @@ public abstract class AstVisitorBase imp
      */
     @Override
     public boolean visit(NativeFunctionImpl node) {
+        node.getNativeSearchExpression().accept(this);
+        return true;
+    }
+    
+    /**
+     * Calls accept on the static operand in the similar search constraint.
+     */
+    @Override
+    public boolean visit(SimilarImpl node) {
+        node.getPathExpression().accept(this);
         return true;
     }
 

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SimilarImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SimilarImpl.java?rev=1583563&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SimilarImpl.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SimilarImpl.java Tue Apr  1 08:04:49 2014
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.oak.query.ast;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.jackrabbit.oak.api.PropertyValue;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.query.index.FilterImpl;
+import org.apache.jackrabbit.oak.spi.query.PropertyValues;
+import org.apache.jackrabbit.oak.spi.query.QueryIndex.FulltextQueryIndex;
+
+/**
+ * Support for "similar(...)
+ */
+public class SimilarImpl extends ConstraintImpl {
+    
+    public static final String NATIVE_LUCENE_LANGUAGE = "lucene";
+    
+    public static final String MORE_LIKE_THIS_PREFIX = "mlt?mlt.fl=:path&mlt.mindf=0&stream.body=";
+    
+    private final String selectorName;
+    private final String propertyName;
+    private final StaticOperandImpl pathExpression;
+    private SelectorImpl selector;
+    
+    SimilarImpl(String selectorName, String propertyName, StaticOperandImpl pathExpression) {
+        this.selectorName = selectorName;
+        this.propertyName = propertyName;
+        this.pathExpression = pathExpression;
+    }
+    
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("similar(");
+        builder.append(quote(selectorName));
+        if (propertyName != null) {
+            builder.append(".").append(propertyName);
+        }
+        builder.append(", ");
+        builder.append(getPathExpression());
+        builder.append(')');
+        return builder.toString();
+    }
+    
+    @Override
+    public boolean evaluate() {
+        // disable evaluation if a fulltext index is used,
+        // and because we don't know how to process native
+        // conditions
+        if (!(selector.getIndex() instanceof FulltextQueryIndex)) {
+            throw new IllegalArgumentException("No full-text index was found that can process the condition " + toString());
+        }
+        // verify the path is readable
+        PropertyValue p = pathExpression.currentValue();
+        String path = p.getValue(Type.STRING);
+        if (selector.getTree(path) == null) {
+            return false;
+        }
+        if (propertyName != null) {
+            if (selector.currentProperty(propertyName) == null) {
+                // property not found
+                return false;
+            }
+        }
+        
+        // we assume the index only returns the requested entries
+        return true;
+    }
+
+    @Override
+    public Set<PropertyExistenceImpl> getPropertyExistenceConditions() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public Map<DynamicOperandImpl, Set<StaticOperandImpl>> getInMap() {
+        return Collections.emptyMap();
+    }
+
+    @Override
+    public void restrict(FilterImpl f) {
+        if (f.getSelector().equals(selector)) {
+            PropertyValue p = pathExpression.currentValue();
+            String path = p.getValue(Type.STRING);
+            String query = MORE_LIKE_THIS_PREFIX + path;
+            PropertyValue v = PropertyValues.newString(query);
+            f.restrictProperty(NativeFunctionImpl.NATIVE_PREFIX + NATIVE_LUCENE_LANGUAGE, Operator.EQUAL, v);
+        }
+    }
+
+    @Override
+    public void restrictPushDown(SelectorImpl s) {
+        if (s.equals(selector)) {
+            selector.restrictSelector(this);
+        }
+    }
+
+    @Override
+    public Set<SelectorImpl> getSelectors() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    boolean accept(AstVisitor v) {
+        return v.visit(this);
+    }
+
+    public void bindSelector(SourceImpl source) {
+        selector = source.getExistingSelector(selectorName);
+    }
+    
+    public StaticOperandImpl getPathExpression() {
+        return pathExpression;
+    }
+
+}
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/Expression.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/Expression.java?rev=1583563&r1=1583562&r2=1583563&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/Expression.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/Expression.java Tue Apr  1 08:04:49 2014
@@ -358,7 +358,39 @@ abstract class Expression {
             return false;
         }
     
-    }    
+    } 
+    
+    /**
+     * A rep:similar condition.
+     */
+    static class Similar extends Expression {
+        
+        final Expression property, path;
+    
+        Similar(Expression property, Expression path) {
+            this.property = property;
+            this.path = path;
+        }
+    
+        @Override
+        public String toString() {
+            StringBuilder buff = new StringBuilder("similar(");
+            buff.append(property);
+            buff.append(", ").append(path).append(')');
+            return buff.toString();
+        }
+    
+        @Override
+        boolean isCondition() {
+            return true;
+        }
+        
+        @Override
+        boolean isName() {
+            return false;
+        }
+    
+    } 
 
     /**
      * A function call.

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/XPathToSQL2Converter.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/XPathToSQL2Converter.java?rev=1583563&r1=1583562&r2=1583563&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/XPathToSQL2Converter.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/XPathToSQL2Converter.java Tue Apr  1 08:04:49 2014
@@ -615,8 +615,12 @@ public class XPathToSQL2Converter {
             Expression.NativeFunction f = new Expression.NativeFunction(selectorName, language, expr);
             return f;
         } else if ("rep:similar".equals(functionName)) {
-             // TODO maybe support rep:similar
-             throw getSyntaxError("rep:similar is not supported");
+            Expression property = parseExpression();
+            read(",");
+            Expression path = parseExpression();
+            read(")");
+            Expression.Similar f = new Expression.Similar(property, path);
+            return f;
         } else if ("rep:spellcheck".equals(functionName)) {
             // TODO maybe support rep:spellcheck as in
             // /jcr:root[rep:spellcheck('${query}')]/(rep:spellcheck())            

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldFactory.java?rev=1583563&r1=1583562&r2=1583563&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldFactory.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldFactory.java Tue Apr  1 08:04:49 2014
@@ -41,6 +41,7 @@ public final class FieldFactory {
     static {
         OAK_TYPE.setIndexed(true);
         OAK_TYPE.setOmitNorms(true);
+        OAK_TYPE.setStored(true);
         OAK_TYPE.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS);
         OAK_TYPE.setTokenized(true);
         OAK_TYPE.freeze();

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/MoreLikeThisHelper.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/MoreLikeThisHelper.java?rev=1583563&r1=1583562&r2=1583563&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/MoreLikeThisHelper.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/MoreLikeThisHelper.java Tue Apr  1 08:04:49 2014
@@ -17,10 +17,21 @@
 package org.apache.jackrabbit.oak.plugins.index.lucene.util;
 
 import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.jackrabbit.oak.plugins.index.lucene.FieldNames;
 import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.document.Document;
 import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexableField;
+import org.apache.lucene.index.Term;
 import org.apache.lucene.queries.mlt.MoreLikeThis;
+import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.TopDocs;
 
 /**
  * Helper class for generating a {@link org.apache.lucene.queries.mlt.MoreLikeThisQuery} from the native query <code>String</code>
@@ -33,6 +44,7 @@ public class MoreLikeThisHelper {
         mlt.setAnalyzer(analyzer);
         try {
             String text = null;
+            String[] fields = {};
             for (String param : mltQueryString.split("&")) {
                 String[] keyValuePair = param.split("=");
                 if (keyValuePair.length != 2 || keyValuePair[0] == null || keyValuePair[1] == null) {
@@ -41,7 +53,7 @@ public class MoreLikeThisHelper {
                     if ("stream.body".equals(keyValuePair[0])) {
                         text = keyValuePair[1];
                     } else if ("mlt.fl".equals(keyValuePair[0])) {
-                        mlt.setFieldNames(keyValuePair[1].split(","));
+                        fields = keyValuePair[1].split(",");
                     } else if ("mlt.mindf".equals(keyValuePair[0])) {
                         mlt.setMinDocFreq(Integer.parseInt(keyValuePair[1]));
                     } else if ("mlt.mintf".equals(keyValuePair[0])) {
@@ -66,7 +78,30 @@ public class MoreLikeThisHelper {
                 }
             }
             if (text != null) {
-                moreLikeThisQuery = mlt.like(new StringReader(text), mlt.getFieldNames()[0]);
+                if (FieldNames.PATH.equals(fields[0])) {
+                    IndexSearcher searcher = new IndexSearcher(reader);
+                    TermQuery q = new TermQuery(new Term(FieldNames.PATH, text));
+                    TopDocs top = searcher.search(q, 1);
+                    if (top.totalHits == 0) {
+                        mlt.setFieldNames(fields);
+                        moreLikeThisQuery = mlt.like(new StringReader(text), mlt.getFieldNames()[0]);
+                    } else{
+                        ScoreDoc d = top.scoreDocs[0];
+                        Document doc = reader.document(d.doc);
+                        List<String> fieldNames = new ArrayList<String>();
+                        for (IndexableField f : doc.getFields()) {
+                            if (!FieldNames.PATH.equals(f.name())) {
+                                fieldNames.add(f.name());
+                            }
+                        }
+                        String[] docFields = fieldNames.toArray(new String[0]);
+                        mlt.setFieldNames(docFields);
+                        moreLikeThisQuery = mlt.like(d.doc);
+                    }
+                } else {
+                    mlt.setFieldNames(fields);
+                    moreLikeThisQuery = mlt.like(new StringReader(text), mlt.getFieldNames()[0]);
+                }
             }
             return moreLikeThisQuery;
         } catch (Exception e) {

Modified: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexQueryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexQueryTest.java?rev=1583563&r1=1583562&r2=1583563&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexQueryTest.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexQueryTest.java Tue Apr  1 08:04:49 2014
@@ -266,22 +266,61 @@ public class LuceneIndexQueryTest extend
     }
 
     @Test
-    public void testNativeMLTQuery() throws Exception {
-        String nativeQueryString = "select [jcr:path] from [nt:base] where native('lucene', 'mlt?stream.body=World&mlt.fl=name&mlt.mindf=0&mlt.mintf=0')";
-
-        Tree tree = root.getTree("/");
-        Tree test = tree.addChild("test");
-        test.addChild("a").setProperty("name", "Hello World, today weather is nice");
-        test.addChild("b").setProperty("name", "Cheers World, today weather is quite nice");
-        tree.addChild("c");
+    public void testRepSimilarAsNativeQuery() throws Exception {
+        String nativeQueryString = "select [jcr:path] from [nt:base] where " + 
+                "native('lucene', 'mlt?stream.body=/test/a&mlt.fl=:path&mlt.mindf=0&mlt.mintf=0')";
+        Tree test = root.getTree("/").addChild("test");
+        test.addChild("a").setProperty("text", "Hello World");
+        test.addChild("b").setProperty("text", "He said Hello and then the world said Hello as well.");
+        test.addChild("c").setProperty("text", "He said Hi.");
+        root.commit();
+        Iterator<String> result = executeQuery(nativeQueryString, "JCR-SQL2").iterator();
+        assertTrue(result.hasNext());
+        assertEquals("/test/a", result.next());
+        assertTrue(result.hasNext());
+        assertEquals("/test/b", result.next());
+        assertFalse(result.hasNext());
+    }
+    
+    @Test
+    public void testRepSimilarQuery() throws Exception {
+        String query = "select [jcr:path] from [nt:base] where similar(., '/test/a')";
+        Tree test = root.getTree("/").addChild("test");
+        test.addChild("a").setProperty("text", "Hello World Hello World");
+        test.addChild("b").setProperty("text", "Hello World");
+        test.addChild("c").setProperty("text", "World");
+        test.addChild("d").setProperty("text", "Hello");
+        test.addChild("e").setProperty("text", "World");
+        test.addChild("f").setProperty("text", "Hello");
+        test.addChild("g").setProperty("text", "World");
+        test.addChild("h").setProperty("text", "Hello");
         root.commit();
+        Iterator<String> result = executeQuery(query, "JCR-SQL2").iterator();
+        assertTrue(result.hasNext());
+        assertEquals("/test/a", result.next());
+        assertTrue(result.hasNext());
+        assertEquals("/test/b", result.next());
+        assertTrue(result.hasNext());
+    }
 
-        Iterator<String> strings = executeQuery(nativeQueryString, "JCR-SQL2").iterator();
-        assertTrue(strings.hasNext());
-        assertEquals("/test/a", strings.next());
-        assertTrue(strings.hasNext());
-        assertEquals("/test/b", strings.next());
-        assertFalse(strings.hasNext());
+    @Test
+    public void testRepSimilarXPathQuery() throws Exception {
+        String query = "//element(*, nt:base)[rep:similar(., '/test/a')]";
+        Tree test = root.getTree("/").addChild("test");
+        test.addChild("a").setProperty("text", "Hello World Hello World");
+        test.addChild("b").setProperty("text", "Hello World");
+        test.addChild("c").setProperty("text", "World");
+        test.addChild("d").setProperty("text", "Hello");
+        test.addChild("e").setProperty("text", "World");
+        test.addChild("f").setProperty("text", "Hello");
+        test.addChild("g").setProperty("text", "World");
+        test.addChild("h").setProperty("text", "Hello");
+        root.commit();
+        Iterator<String> result = executeQuery(query, "xpath").iterator();
+        assertTrue(result.hasNext());
+        assertEquals("/test/a", result.next());
+        assertTrue(result.hasNext());
+        assertEquals("/test/b", result.next());
     }
 
     @Test