You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by mr...@apache.org on 2005/04/04 16:30:50 UTC

svn commit: r160047 - in incubator/jackrabbit/trunk/src: grammar/sql/ java/org/apache/jackrabbit/core/search/ java/org/apache/jackrabbit/core/search/lucene/ java/org/apache/jackrabbit/core/search/sql/ java/org/apache/jackrabbit/core/search/xpath/ test/org/apache/jackrabbit/core/search/

Author: mreutegg
Date: Mon Apr  4 07:30:48 2005
New Revision: 160047

URL: http://svn.apache.org/viewcvs?view=rev&rev=160047
Log:
JCR-56: allow case insensitive searches

Modified:
    incubator/jackrabbit/trunk/src/grammar/sql/JCRSQL.jjt
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/TextsearchQueryNode.java
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/lucene/FieldNames.java
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/lucene/LuceneQueryBuilder.java
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/lucene/NodeIndexer.java
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/sql/ASTContainsExpression.java
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/sql/JCRSQLQueryBuilder.java
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/sql/QueryFormat.java
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/xpath/QueryFormat.java
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/xpath/XPathQueryBuilder.java
    incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/core/search/FulltextQueryTest.java

Modified: incubator/jackrabbit/trunk/src/grammar/sql/JCRSQL.jjt
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/grammar/sql/JCRSQL.jjt?view=diff&r1=160046&r2=160047
==============================================================================
--- incubator/jackrabbit/trunk/src/grammar/sql/JCRSQL.jjt (original)
+++ incubator/jackrabbit/trunk/src/grammar/sql/JCRSQL.jjt Mon Apr  4 07:30:48 2005
@@ -428,9 +428,15 @@
 void ContainsExpression() :
 {
   Token t = null;
+  QName name = null;
 }
 {
-  <CONTAINS> <LEFT_PAREN> t = <CHAR_STRING_LITERAL> { jjtThis.setQuery(t.image.substring(1, t.image.length() - 1).replaceAll("''", "'")); } <RIGHT_PAREN>
+  <CONTAINS>
+  <LEFT_PAREN>
+  // currently optional
+  ( ( <ASTERISK> | (name = Identifier() { jjtThis.setPropertyName(name); }) ) "," )?
+  t = <CHAR_STRING_LITERAL> { jjtThis.setQuery(t.image.substring(1, t.image.length() - 1).replaceAll("''", "'")); }
+  <RIGHT_PAREN>
 }
 
 void Literal() :
@@ -546,7 +552,7 @@
     )
   )
   {
-    return name;
+    return jjtThis.getName();
   }
 }
 

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/TextsearchQueryNode.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/TextsearchQueryNode.java?view=diff&r1=160046&r2=160047
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/TextsearchQueryNode.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/TextsearchQueryNode.java Mon Apr  4 07:30:48 2005
@@ -16,6 +16,8 @@
  */
 package org.apache.jackrabbit.core.search;
 
+import org.apache.jackrabbit.core.QName;
+
 /**
  * Implements a query node that defines a textsearch clause.
  */
@@ -27,15 +29,37 @@
     private final String query;
 
     /**
+     * Limits the scope of this textsearch clause to properties with this name.
+     * If <code>null</code> the scope of this textsearch clause is the fulltext
+     * index of all properties of a node.
+     */
+    private QName propertyName;
+
+    /**
      * Creates a new <code>TextsearchQueryNode</code> with a <code>parent</code>
-     * and a textsearch <code>query</code> statement.
+     * and a textsearch <code>query</code> statement. The scope of the query
+     * is the fulltext index of the node, that contains all properties.
      *
      * @param parent the parent node of this query node.
      * @param query  the textsearch statement.
      */
     public TextsearchQueryNode(QueryNode parent, String query) {
+        this(parent, query, null);
+    }
+
+    /**
+     * Creates a new <code>TextsearchQueryNode</code> with a <code>parent</code>
+     * and a textsearch <code>query</code> statement. The scope of the query
+     * is property with name <code>propertyName</code>.
+     *
+     * @param parent the parent node of this query node.
+     * @param query  the textsearch statement.
+     * @param propertyName scope of the fulltext search.
+     */
+    public TextsearchQueryNode(QueryNode parent, String query, QName propertyName) {
         super(parent);
         this.query = query;
+        this.propertyName = propertyName;
     }
 
     /**
@@ -63,4 +87,23 @@
         return query;
     }
 
+    /**
+     * Returns a property name if the scope is limited to just a single property
+     * or <code>null</code> if the scope is spawned across all properties of a
+     * node.
+     *
+     * @return property name or <code>null</code>.
+     */
+    public QName getPropertyName() {
+        return propertyName;
+    }
+
+    /**
+     * Sets a new name as the search scope for this fulltext query.
+     *
+     * @param property the name of the property.
+     */
+    public void setPropertyName(QName property) {
+        this.propertyName = property;
+    }
 }

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/lucene/FieldNames.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/lucene/FieldNames.java?view=diff&r1=160046&r2=160047
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/lucene/FieldNames.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/lucene/FieldNames.java Mon Apr  4 07:30:48 2005
@@ -35,6 +35,11 @@
     public static final String FULLTEXT = "_:FULLTEXT";
 
     /**
+     * Prefix for all field names that are fulltext indexed by property name.
+     */
+    public static final String FULLTEXT_PREFIX = "FULL:";
+
+    /**
      * Name of the field that contains the UUID of the parent node. Terms are
      * stored and but not tokenized.
      */

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/lucene/LuceneQueryBuilder.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/lucene/LuceneQueryBuilder.java?view=diff&r1=160046&r2=160047
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/lucene/LuceneQueryBuilder.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/lucene/LuceneQueryBuilder.java Mon Apr  4 07:30:48 2005
@@ -16,7 +16,6 @@
  */
 package org.apache.jackrabbit.core.search.lucene;
 
-import org.apache.jackrabbit.core.Constants;
 import org.apache.jackrabbit.core.IllegalNameException;
 import org.apache.jackrabbit.core.MalformedPathException;
 import org.apache.jackrabbit.core.NoPrefixDeclaredException;
@@ -304,8 +303,19 @@
 
     public Object visit(TextsearchQueryNode node, Object data) {
         try {
+            String fieldname;
+            if (node.getPropertyName() == null) {
+                // fulltext on node
+                fieldname = FieldNames.FULLTEXT;
+            } else {
+                StringBuffer tmp = new StringBuffer();
+                tmp.append(nsMappings.getPrefix(node.getPropertyName().getNamespaceURI()));
+                tmp.append(":").append(FieldNames.FULLTEXT_PREFIX);
+                tmp.append(node.getPropertyName().getLocalName());
+                fieldname = tmp.toString();
+            }
             org.apache.lucene.queryParser.QueryParser parser
-                    = new org.apache.lucene.queryParser.QueryParser(FieldNames.FULLTEXT, analyzer);
+                    = new org.apache.lucene.queryParser.QueryParser(fieldname, analyzer);
             parser.setOperator(org.apache.lucene.queryParser.QueryParser.DEFAULT_OPERATOR_AND);
             // replace unescaped ' with " and escaped ' with just '
             StringBuffer query = new StringBuffer();
@@ -339,6 +349,8 @@
                 }
             }
             return parser.parse(query.toString());
+        } catch (NamespaceException e) {
+            exceptions.add(e);
         } catch (ParseException e) {
             exceptions.add(e);
         }

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/lucene/NodeIndexer.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/lucene/NodeIndexer.java?view=diff&r1=160046&r2=160047
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/lucene/NodeIndexer.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/lucene/NodeIndexer.java Mon Apr  4 07:30:48 2005
@@ -355,7 +355,7 @@
      */
     protected void addStringValue(Document doc, String fieldName, Object internalValue) {
         String stringValue = String.valueOf(internalValue);
-        
+
         // simple String
         doc.add(new Field(fieldName,
                 stringValue,
@@ -365,6 +365,15 @@
         // also create fulltext index of this value
         doc.add(new Field(FieldNames.FULLTEXT,
                 stringValue,
+                false,
+                true,
+                true));
+        // create fulltext index on property
+        int idx = fieldName.indexOf(':');
+        fieldName = fieldName.substring(0, idx + 1) +
+                FieldNames.FULLTEXT_PREFIX +
+                fieldName.substring(idx + 1);
+        doc.add(new Field(fieldName, stringValue,
                 false,
                 true,
                 true));

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/sql/ASTContainsExpression.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/sql/ASTContainsExpression.java?view=diff&r1=160046&r2=160047
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/sql/ASTContainsExpression.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/sql/ASTContainsExpression.java Mon Apr  4 07:30:48 2005
@@ -16,9 +16,14 @@
  */
 package org.apache.jackrabbit.core.search.sql;
 
+import org.apache.jackrabbit.core.QName;
+
 public class ASTContainsExpression extends SimpleNode {
+
     private String query;
 
+    private QName property;
+
     public ASTContainsExpression(int id) {
         super(id);
     }
@@ -35,6 +40,13 @@
         this.query = query;
     }
 
+    public QName getPropertyName() {
+        return property;
+    }
+
+    public void setPropertyName(QName property) {
+        this.property = property;
+    }
 
     /**
      * Accept the visitor. *

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/sql/JCRSQLQueryBuilder.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/sql/JCRSQLQueryBuilder.java?view=diff&r1=160046&r2=160047
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/sql/JCRSQLQueryBuilder.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/sql/JCRSQLQueryBuilder.java Mon Apr  4 07:30:48 2005
@@ -420,7 +420,7 @@
 
     public Object visit(ASTContainsExpression node, Object data) {
         NAryQueryNode parent = (NAryQueryNode) data;
-        parent.addOperand(new TextsearchQueryNode(parent, node.getQuery()));
+        parent.addOperand(new TextsearchQueryNode(parent, node.getQuery(), node.getPropertyName()));
         return parent;
     }
 

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/sql/QueryFormat.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/sql/QueryFormat.java?view=diff&r1=160046&r2=160047
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/sql/QueryFormat.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/sql/QueryFormat.java Mon Apr  4 07:30:48 2005
@@ -254,7 +254,18 @@
         StringBuffer sb = (StringBuffer) data;
         // escape quote
         String query = node.getQuery().replaceAll("'", "''");
-        sb.append("CONTAINS('").append(query).append("')");
+        sb.append("CONTAINS(");
+        if (node.getPropertyName() == null) {
+            sb.append("*");
+        } else {
+            try {
+                appendName(node.getPropertyName(), resolver, sb);
+            } catch (NoPrefixDeclaredException e) {
+                exceptions.add(e);
+            }
+        }
+        sb.append(", '");
+        sb.append(query).append("')");
         return sb;
     }
 

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/xpath/QueryFormat.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/xpath/QueryFormat.java?view=diff&r1=160046&r2=160047
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/xpath/QueryFormat.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/xpath/QueryFormat.java Mon Apr  4 07:30:48 2005
@@ -211,7 +211,13 @@
         StringBuffer sb = (StringBuffer) data;
         try {
             sb.append(XPathQueryBuilder.JCRFN_CONTAINS.toJCRName(resolver));
-            sb.append("('");
+            sb.append("(");
+            if (node.getPropertyName() == null) {
+                sb.append(".");
+            } else {
+                sb.append(ISO9075.encode(node.getPropertyName()).toJCRName(resolver));
+            }
+            sb.append(", '");
             sb.append(node.getQuery().replaceAll("'", "''"));
             sb.append("')");
         } catch (NoPrefixDeclaredException e) {

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/xpath/XPathQueryBuilder.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/xpath/XPathQueryBuilder.java?view=diff&r1=160046&r2=160047
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/xpath/XPathQueryBuilder.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/xpath/XPathQueryBuilder.java Mon Apr  4 07:30:48 2005
@@ -301,7 +301,8 @@
                     if (queryNode.getType() == QueryNode.TYPE_RELATION
                             || queryNode.getType() == QueryNode.TYPE_DEREF
                             || queryNode.getType() == QueryNode.TYPE_ORDER
-                            || queryNode.getType() == QueryNode.TYPE_PATH) {
+                            || queryNode.getType() == QueryNode.TYPE_PATH
+                            || queryNode.getType() == QueryNode.TYPE_TEXTSEARCH) {
                         // traverse
                         node.childrenAccept(this, queryNode);
                     } else if (queryNode.getType() == QueryNode.TYPE_NOT) {
@@ -324,7 +325,9 @@
                     }
                 } else {
                     if (queryNode.getType() == QueryNode.TYPE_PATH) {
-                        queryNode = createLocationStep(node, (PathQueryNode) queryNode);
+                        queryNode = createLocationStep(node, (NAryQueryNode) queryNode);
+                    } else if (queryNode.getType() == QueryNode.TYPE_TEXTSEARCH) {
+                        // ignore
                     } else {
                         exceptions.add(new InvalidQueryException("Only attribute axis is allowed in predicate"));
                     }
@@ -334,6 +337,7 @@
                 if (queryNode.getType() == QueryNode.TYPE_LOCATION
                         || queryNode.getType() == QueryNode.TYPE_DEREF
                         || queryNode.getType() == QueryNode.TYPE_RELATION
+                        || queryNode.getType() == QueryNode.TYPE_TEXTSEARCH
                         || queryNode.getType() == QueryNode.TYPE_PATH) {
                     createNodeTest(node, queryNode);
                 } else if (queryNode.getType() == QueryNode.TYPE_ORDER) {
@@ -438,7 +442,7 @@
      * @param parent the parent <code>PathQueryNode</code>.
      * @return the created <code>LocationStepQueryNode</code>.
      */
-    private LocationStepQueryNode createLocationStep(SimpleNode node, PathQueryNode parent) {
+    private LocationStepQueryNode createLocationStep(SimpleNode node, NAryQueryNode parent) {
         LocationStepQueryNode queryNode = null;
         boolean descenant = false;
         Node p = node.jjtGetParent();
@@ -446,7 +450,7 @@
             SimpleNode c = (SimpleNode) p.jjtGetChild(i);
             if (c == node) {
                 queryNode = new LocationStepQueryNode(parent, null, descenant);
-                parent.addPathStep(queryNode);
+                parent.addOperand(queryNode);
                 break;
             }
             descenant = (c.getId() == JJTSLASHSLASH
@@ -461,7 +465,7 @@
     /**
      * Assigns a QName to one of the follwing QueryNodes:
      * {@link RelationQueryNode}, {@link DerefQueryNode}, {@link RelationQueryNode},
-     * {@link PathQueryNode}, {@link OrderQueryNode}.
+     * {@link PathQueryNode}, {@link OrderQueryNode}, {@link TextsearchQueryNode}.
      *
      * @param node      the current node in the xpath syntax tree.
      * @param queryNode the query node.
@@ -489,6 +493,9 @@
                     } else if (queryNode.getType() == QueryNode.TYPE_ORDER) {
                         QName name = ISO9075.decode(QName.fromJCRName(child.getValue(), resolver));
                         root.getOrderNode().addOrderSpec(name, true);
+                    } else if (queryNode.getType() == QueryNode.TYPE_TEXTSEARCH) {
+                        QName name = ISO9075.decode(QName.fromJCRName(child.getValue(), resolver));
+                        ((TextsearchQueryNode) queryNode).setPropertyName(name);
                     }
                 } catch (IllegalNameException e) {
                     exceptions.add(new InvalidQueryException("Illegal name: " + child.getValue()));
@@ -518,20 +525,27 @@
         }
         // get operation type
         String opType = node.getValue();
-        // todo distinguish value vs. general comparison
         int type = 0;
         if (opType.equals(OP_EQ)) {
             type = RelationQueryNode.OPERATION_EQ_VALUE;
         } else if (opType.equals(OP_SIGN_EQ)) {
             type = RelationQueryNode.OPERATION_EQ_GENERAL;
-        } else if (opType.equals(OP_GE) || opType.equals(OP_SIGN_GE)) {
-            type = RelationQueryNode.OPERATION_GE_VALUE;
-        } else if (opType.equals(OP_GT) || opType.equals(OP_SIGN_GT)) {
+        } else if (opType.equals(OP_GT)) {
             type = RelationQueryNode.OPERATION_GT_VALUE;
-        } else if (opType.equals(OP_LE) || opType.equals(OP_SIGN_LE)) {
+        } else if (opType.equals(OP_SIGN_GT)) {
+            type = RelationQueryNode.OPERATION_GT_GENERAL;
+        } else if (opType.equals(OP_GE)) {
+            type = RelationQueryNode.OPERATION_GE_VALUE;
+        } else if (opType.equals(OP_SIGN_GE)) {
+            type = RelationQueryNode.OPERATION_GE_GENERAL;
+        } else if (opType.equals(OP_LE)) {
             type = RelationQueryNode.OPERATION_LE_VALUE;
-        } else if (opType.equals(OP_LT) || opType.equals(OP_SIGN_LT)) {
+        } else if (opType.equals(OP_SIGN_LE)) {
+            type = RelationQueryNode.OPERATION_LE_GENERAL;
+        } else if (opType.equals(OP_LT)) {
             type = RelationQueryNode.OPERATION_LT_VALUE;
+        } else if (opType.equals(OP_SIGN_LT)) {
+            type = RelationQueryNode.OPERATION_LT_GENERAL;
         } else if (opType.equals(OP_NE)) {
             type = RelationQueryNode.OPERATION_NE_VALUE;
         } else if (opType.equals(OP_SIGN_NE)) {
@@ -668,7 +682,29 @@
                 }
             } else if (JCRFN_CONTAINS.toJCRName(resolver).equals(fName)) {
                 // check number of arguments
-                if (node.jjtGetNumChildren() == 2) {
+                if (node.jjtGetNumChildren() == 3) {
+                    if (queryNode instanceof NAryQueryNode) {
+                        SimpleNode literal = (SimpleNode) node.jjtGetChild(2).jjtGetChild(0);
+                        if (literal.getId() == JJTSTRINGLITERAL) {
+                            String value = literal.getValue();
+                            if (value.charAt(0) == '"') {
+                                value = value.replaceAll("\"\"", "\"");
+                            } else {
+                                value = value.replaceAll("''", "'");
+                            }
+                            // strip quotes
+                            value = value.substring(1, value.length() - 1);
+                            TextsearchQueryNode contains = new TextsearchQueryNode(queryNode, value);
+                            // assign property name
+                            SimpleNode path = (SimpleNode) node.jjtGetChild(1);
+                            path.jjtAccept(this, contains);
+                            ((NAryQueryNode) queryNode).addOperand(contains);
+                        } else {
+                            exceptions.add(new InvalidQueryException("Wrong argument type for jcrfn:contains"));
+                        }
+                    }
+                // todo first parameter is currently optional -> remove later
+                } else if (node.jjtGetNumChildren() == 2) {
                     SimpleNode literal = (SimpleNode) node.jjtGetChild(1).jjtGetChild(0);
                     if (queryNode instanceof NAryQueryNode) {
                         if (literal.getId() == JJTSTRINGLITERAL) {

Modified: incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/core/search/FulltextQueryTest.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/core/search/FulltextQueryTest.java?view=diff&r1=160046&r2=160047
==============================================================================
--- incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/core/search/FulltextQueryTest.java (original)
+++ incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/core/search/FulltextQueryTest.java Mon Apr  4 07:30:48 2005
@@ -17,6 +17,7 @@
 package org.apache.jackrabbit.core.search;
 
 import javax.jcr.Node;
+import javax.jcr.RepositoryException;
 import javax.jcr.query.Query;
 import javax.jcr.query.QueryResult;
 
@@ -148,5 +149,74 @@
         checkResult(result, 2);
     }
 
+    public void testContainsStarSQL() throws RepositoryException {
+        Node n = testRootNode.addNode("node1");
+        n.setProperty("title", new String[]{"tEst text"});
+        n.setProperty("mytext", new String[]{"The quick brown Fox jumps over the lazy dog."});
+
+        n = testRootNode.addNode("node2");
+        n.setProperty("title", new String[]{"The quick brown Fox jumps over the lazy dog."});
+        n.setProperty("mytext", new String[]{"text text"});
+
+        testRootNode.save();
+
+        String sql = "SELECT * FROM nt:unstructured"
+                + " WHERE jcr:path LIKE '" + testRoot + "/%"
+                + "' AND CONTAINS(*, 'fox jumps')";
+        Query q = superuser.getWorkspace().getQueryManager().createQuery(sql, Query.SQL);
+        checkResult(q.execute(), 2);
+    }
+
+    public void testContainsStarXPath() throws RepositoryException {
+        Node n = testRootNode.addNode("node1");
+        n.setProperty("title", new String[]{"tEst text"});
+        n.setProperty("mytext", new String[]{"The quick brown Fox jumps over the lazy dog."});
+
+        n = testRootNode.addNode("node2");
+        n.setProperty("title", new String[]{"The quick brown Fox jumps over the lazy dog."});
+        n.setProperty("mytext", new String[]{"text text"});
+
+        testRootNode.save();
+
+        String sql = "/jcr:root" + testRoot + "/element(*, nt:unstructured)"
+                + "[jcrfn:contains(., 'quick fox')]";
+        Query q = superuser.getWorkspace().getQueryManager().createQuery(sql, Query.XPATH);
+        checkResult(q.execute(), 2);
+    }
+
+    public void testContainsPropScopeSQL() throws RepositoryException {
+        Node n = testRootNode.addNode("node1");
+        n.setProperty("title", new String[]{"tEst text"});
+        n.setProperty("mytext", new String[]{"The quick brown Fox jumps over the lazy dog."});
+
+        n = testRootNode.addNode("node2");
+        n.setProperty("title", new String[]{"The quick brown Fox jumps over the lazy dog."});
+        n.setProperty("mytext", new String[]{"text text"});
+
+        testRootNode.save();
+
+        String sql = "SELECT * FROM nt:unstructured"
+                + " WHERE jcr:path LIKE '" + testRoot + "/%"
+                + "' AND CONTAINS(title, 'fox jumps')";
+        Query q = superuser.getWorkspace().getQueryManager().createQuery(sql, Query.SQL);
+        checkResult(q.execute(), 1);
+    }
+
+    public void testContainsPropScopeXPath() throws RepositoryException {
+        Node n = testRootNode.addNode("node1");
+        n.setProperty("title", new String[]{"tEst text"});
+        n.setProperty("mytext", new String[]{"The quick brown Fox jumps over the lazy dog."});
+
+        n = testRootNode.addNode("node2");
+        n.setProperty("title", new String[]{"The quick brown Fox jumps over the lazy dog."});
+        n.setProperty("mytext", new String[]{"text text"});
+
+        testRootNode.save();
+
+        String sql = "/jcr:root" + testRoot + "/element(*, nt:unstructured)"
+                + "[jcrfn:contains(@title, 'quick fox')]";
+        Query q = superuser.getWorkspace().getQueryManager().createQuery(sql, Query.XPATH);
+        checkResult(q.execute(), 1);
+    }
 
 }