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/01/24 11:20:43 UTC

svn commit: r126276 - 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/test/search

Author: mreutegg
Date: Mon Jan 24 02:20:40 2005
New Revision: 126276

URL: http://svn.apache.org/viewcvs?view=rev&rev=126276
Log:
Implemented proper support for IS [NOT] NULL queries.
Modified:
   incubator/jackrabbit/trunk/src/grammar/sql/JCRSQL.jjt
   incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/Constants.java
   incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/NAryQueryNode.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/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/test/search/SimpleQueryTest.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&rev=126276&p1=incubator/jackrabbit/trunk/src/grammar/sql/JCRSQL.jjt&r1=126275&p2=incubator/jackrabbit/trunk/src/grammar/sql/JCRSQL.jjt&r2=126276
==============================================================================
--- incubator/jackrabbit/trunk/src/grammar/sql/JCRSQL.jjt	(original)
+++ incubator/jackrabbit/trunk/src/grammar/sql/JCRSQL.jjt	Mon Jan 24 02:20:40 2005
@@ -325,7 +325,11 @@
       )
     )
   |
-    (<IS> (<NOT> { jjtThis.setNegate(true); })? <NULL> { jjtThis.setOperationType(Constants.OPERATION_NULL); } )
+    (<IS> (<NOT> { jjtThis.setNegate(true); })? <NULL>
+      {
+        jjtThis.setOperationType(jjtThis.isNegate() ? Constants.OPERATION_NOT_NULL : Constants.OPERATION_NULL); 
+      }
+    )
   )
 }
 

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/Constants.java
Url: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/Constants.java?view=diff&rev=126276&p1=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/Constants.java&r1=126275&p2=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/Constants.java&r2=126276
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/Constants.java	(original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/Constants.java	Mon Jan 24 02:20:40 2005
@@ -93,7 +93,12 @@
     public static int OPERATION_IN = 18;
 
     /**
-     * is null operation: identifier IS [ NOT ] NULL
+     * is null operation: identifier IS NULL
      */
     public static int OPERATION_NULL = 19;
+
+    /**
+     * is not null operation: identifier IS NOT NULL
+     */
+    public static int OPERATION_NOT_NULL = 20;
 }

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/NAryQueryNode.java
Url: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/NAryQueryNode.java?view=diff&rev=126276&p1=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/NAryQueryNode.java&r1=126275&p2=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/NAryQueryNode.java&r2=126276
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/NAryQueryNode.java	(original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/NAryQueryNode.java	Mon Jan 24 02:20:40 2005
@@ -73,6 +73,21 @@
     }
 
     /**
+     * Removes an <code>operand</code> (child node) from this query node.
+     *
+     * @param operand the child to remove.
+     * @return <code>true</code> if the operand was in the list of child nodes
+     *  and has been removed; <code>false</code> if this node does not contain
+     *  <code>operand</code> as a child node.
+     */
+    public boolean removeOperand(QueryNode operand) {
+        if (operands == null) {
+            return false;
+        }
+        return operands.remove(operand);
+    }
+
+    /**
      * Returns an array of currently set <code>QueryNode</code> operands of this
      * <code>QueryNode</code>. Returns an empty array if no operands are set.
      *

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&rev=126276&p1=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/lucene/LuceneQueryBuilder.java&r1=126275&p2=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/lucene/LuceneQueryBuilder.java&r2=126276
==============================================================================
--- 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 Jan 24 02:20:40 2005
@@ -43,23 +43,41 @@
 import java.util.Arrays;
 
 /**
+ * Implements a query builder that takes an abstract query tree and creates
+ * a lucene {@link org.apache.lucene.search.Query} tree that can be executed
+ * on an index.
+ * todo introduce a node type hierarchy for efficient translation of NodeTypeQueryNode
  */
 class LuceneQueryBuilder implements QueryNodeVisitor {
 
+    /** Logger for this class */
     private static final Logger log = Logger.getLogger(LuceneQueryBuilder.class);
 
+    /** QName for jcr:primaryType */
     private static QName primaryType = new QName(NamespaceRegistryImpl.NS_JCR_URI, "primaryType");
 
+    /** Root node of the abstract query tree */
     private QueryRootNode root;
 
+    /** Session of the user executing this query */
     private SessionImpl session;
 
+    /** Namespace mappings to internal prefixes */
     private NamespaceMappings nsMappings;
 
+    /** The analyzer instance to use for contains function query parsing */
     private Analyzer analyzer;
 
+    /** Exceptions thrown during tree translation */
     private List exceptions = new ArrayList();
 
+    /**
+     * Creates a new <code>LuceneQueryBuilder</code> instance.
+     * @param root the root node of the abstract query tree.
+     * @param session of the user executing this query.
+     * @param nsMappings namespace resolver for internal prefixes.
+     * @param analyzer for parsing the query statement of the contains function.
+     */
     private LuceneQueryBuilder(QueryRootNode root,
                                SessionImpl session,
                                NamespaceMappings nsMappings,
@@ -70,6 +88,16 @@
         this.analyzer = analyzer;
     }
 
+    /**
+     * Creates a lucene {@link org.apache.lucene.search.Query} tree from an
+     * abstract query tree.
+     * @param root the root node of the abstract query tree.
+     * @param session of the user executing the query.
+     * @param nsMappings namespace resolver for internal prefixes.
+     * @param analyzer for parsing the query statement of the contains function.
+     * @return the lucene query tree.
+     * @throws RepositoryException if an error occurs during the translation.
+     */
     public static Query createQuery(QueryRootNode root,
                                     SessionImpl session,
                                     NamespaceMappings nsMappings,
@@ -85,15 +113,22 @@
             for (Iterator it = builder.exceptions.iterator(); it.hasNext();) {
                 msg.append(it.next().toString()).append('\n');
             }
-            throw new RepositoryException("Exception parsing query: " + msg.toString());
+            throw new RepositoryException("Exception building query: " + msg.toString());
         }
         return q;
     }
 
+    /**
+     * Starts the tree traversal and returns the lucene
+     * {@link org.apache.lucene.search.Query}.
+     * @return the lucene <code>Query</code>.
+     */
     private Query createLuceneQuery() {
         return (Query) root.accept(this, null);
     }
 
+    //---------------------< QueryNodeVisitor interface >-----------------------
+
     public Object visit(QueryRootNode node, Object data) {
         BooleanQuery root = new BooleanQuery();
 
@@ -333,8 +368,11 @@
 
     public Object visit(RelationQueryNode node, Object data) {
         Query query;
-        String stringValue;
+        String stringValue = null;
         switch (node.getType()) {
+            case 0:
+                // not set: either IS NULL or IS NOT NULL
+                break;
             case Constants.TYPE_DATE:
                 stringValue = DateField.dateToString(node.getDateValue());
                 break;
@@ -353,8 +391,10 @@
         }
 
         String field = "";
+        String primaryTypeField = "";
         try {
             field = node.getProperty().toJCRName(nsMappings);
+            primaryTypeField = primaryType.toJCRName(nsMappings);
         } catch (NoPrefixDeclaredException e) {
             // should never happen
             exceptions.add(e);
@@ -388,6 +428,15 @@
                 notQuery.add(new MatchAllQuery(field), false, false);
                 notQuery.add(new TermQuery(new Term(field, stringValue)), false, true);
                 query = notQuery;
+                break;
+            case Constants.OPERATION_NULL:
+                notQuery = new BooleanQuery();
+                notQuery.add(new MatchAllQuery(primaryTypeField), false, false);
+                notQuery.add(new MatchAllQuery(field), false, true);
+                query = notQuery;
+                break;
+            case Constants.OPERATION_NOT_NULL:
+                query = new MatchAllQuery(field);
                 break;
             default:
                 throw new IllegalArgumentException("Unknown relation operation: "

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&rev=126276&p1=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/lucene/NodeIndexer.java&r1=126275&p2=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/lucene/NodeIndexer.java&r2=126276
==============================================================================
--- 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 Jan 24 02:20:40 2005
@@ -16,10 +16,18 @@
  */
 package org.apache.jackrabbit.core.search.lucene;
 
-import org.apache.jackrabbit.core.*;
-import org.apache.jackrabbit.core.state.*;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
+import org.apache.jackrabbit.core.state.NodeState;
+import org.apache.jackrabbit.core.state.ItemStateManager;
+import org.apache.jackrabbit.core.state.NoSuchItemStateException;
+import org.apache.jackrabbit.core.state.ItemStateException;
+import org.apache.jackrabbit.core.state.PropertyState;
+import org.apache.jackrabbit.core.NodeId;
+import org.apache.jackrabbit.core.NoPrefixDeclaredException;
+import org.apache.jackrabbit.core.PropertyId;
+import org.apache.jackrabbit.core.InternalValue;
+import org.apache.jackrabbit.core.QName;
 
 import javax.jcr.NamespaceException;
 import javax.jcr.PropertyType;

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&rev=126276&p1=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/sql/JCRSQLQueryBuilder.java&r1=126275&p2=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/sql/JCRSQLQueryBuilder.java&r2=126276
==============================================================================
--- 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 Jan 24 02:20:40 2005
@@ -182,15 +182,11 @@
 
         return node.childrenAccept(new DefaultParserVisitor() {
             public Object visit(ASTIdentifier node, Object data) {
-                try {
-                    if (!node.getName().equals(NodeTypeRegistry.NT_BASE.toJCRName(resolver))) {
-                        // node is either primary or mixin node type
-                        NodeTypeQueryNode nodeType
-                                = new NodeTypeQueryNode(constraintNode, node.getName());
-                        constraintNode.addOperand(nodeType);
-                    }
-                } catch (NoPrefixDeclaredException e) {
-                    throw new IllegalArgumentException("No prefix declared for name: " + node.getName());
+                if (!node.getName().equals(NodeTypeRegistry.NT_BASE)) {
+                    // node is either primary or mixin node type
+                    NodeTypeQueryNode nodeType
+                            = new NodeTypeQueryNode(constraintNode, node.getName());
+                    constraintNode.addOperand(nodeType);
                 }
                 return data;
             }
@@ -204,10 +200,6 @@
     public Object visit(ASTPredicate node, Object data) {
         NAryQueryNode parent = (NAryQueryNode) data;
 
-        if (node.isNegate()) {
-            parent = new NotQueryNode(parent);
-        }
-
         int type = node.getOperationType();
         QueryNode predicateNode = null;
 
@@ -271,12 +263,16 @@
                     in.addOperand(rel);
                 }
                 predicateNode = in;
-            } else if (type == Constants.OPERATION_NULL) {
+            } else if (type == Constants.OPERATION_NULL
+                    || type == Constants.OPERATION_NOT_NULL) {
+                // create a dummy literal
                 ASTLiteral star = new ASTLiteral(JCRSQLParserTreeConstants.JJTLITERAL);
                 star.setType(Constants.TYPE_STRING);
                 star.setValue("%");
                 predicateNode = createRelationQueryNode(parent,
-                        identifier, Constants.OPERATION_LIKE, star);
+                        identifier, type, star);
+            } else {
+                throw new IllegalArgumentException("Unknown operation type: " + type);
             }
         } catch (ArrayIndexOutOfBoundsException e) {
             throw new IllegalArgumentException("Too few arguments in predicate");

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&rev=126276&p1=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/sql/QueryFormat.java&r1=126275&p2=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/sql/QueryFormat.java&r2=126276
==============================================================================
--- 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 Jan 24 02:20:40 2005
@@ -317,37 +317,33 @@
 
         if (node.getOperation() == OPERATION_EQ) {
             sb.append(" = ");
+            appendValue(node, sb);
         } else if (node.getOperation() == OPERATION_GE) {
             sb.append(" >= ");
+            appendValue(node, sb);
         } else if (node.getOperation() == OPERATION_GT) {
             sb.append(" > ");
+            appendValue(node, sb);
         } else if (node.getOperation() == OPERATION_LE) {
             sb.append(" <= ");
+            appendValue(node, sb);
         } else if (node.getOperation() == OPERATION_LIKE) {
             sb.append(" LIKE ");
+            appendValue(node, sb);
         } else if (node.getOperation() == OPERATION_LT) {
             sb.append(" < ");
+            appendValue(node, sb);
         } else if (node.getOperation() == OPERATION_NE) {
             sb.append(" <> ");
+            appendValue(node, sb);
+        } else if (node.getOperation() == OPERATION_NULL) {
+            sb.append(" IS NULL");
+        } else if (node.getOperation() == OPERATION_NOT_NULL) {
+            sb.append(" IS NOT NULL");
         } else {
             exceptions.add(new InvalidQueryException("Invalid operation: " + node.getOperation()));
         }
 
-
-        if (node.getType() == TYPE_LONG) {
-            sb.append(node.getLongValue());
-        } else if (node.getType() == TYPE_DOUBLE) {
-            sb.append(node.getDoubleValue());
-        } else if (node.getType() == TYPE_STRING) {
-            sb.append("'").append(node.getStringValue().replaceAll("'", "''")).append("'");
-        } else if (node.getType() == TYPE_DATE || node.getType() == TYPE_TIMESTAMP) {
-            Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
-            cal.setTime(node.getDateValue());
-            sb.append("TIMESTAMP '").append(ISO8601.format(cal)).append("'");
-        } else {
-            exceptions.add(new InvalidQueryException("Invalid type: " + node.getType()));
-        }
-
         if (node.getOperation() == OPERATION_LIKE && node.getStringValue().indexOf('\\') > -1) {
             sb.append(" ESCAPE '\\'");
         }
@@ -404,5 +400,22 @@
         if (quote) {
             b.append('"');
         }
+    }
+
+    private void appendValue(RelationQueryNode node, StringBuffer b) {
+        if (node.getType() == TYPE_LONG) {
+            b.append(node.getLongValue());
+        } else if (node.getType() == TYPE_DOUBLE) {
+            b.append(node.getDoubleValue());
+        } else if (node.getType() == TYPE_STRING) {
+            b.append("'").append(node.getStringValue().replaceAll("'", "''")).append("'");
+        } else if (node.getType() == TYPE_DATE || node.getType() == TYPE_TIMESTAMP) {
+            Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+            cal.setTime(node.getDateValue());
+            b.append("TIMESTAMP '").append(ISO8601.format(cal)).append("'");
+        } else {
+            exceptions.add(new InvalidQueryException("Invalid type: " + node.getType()));
+        }
+
     }
 }

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&rev=126276&p1=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/xpath/QueryFormat.java&r1=126275&p2=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/xpath/QueryFormat.java&r2=126276
==============================================================================
--- 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 Jan 24 02:20:40 2005
@@ -258,46 +258,40 @@
         StringBuffer sb = (StringBuffer) data;
         try {
 
-            int propIdx = sb.length();
-            sb.append("@" + ISO9075.encode(node.getProperty()).toJCRName(resolver));
+            //int propIdx = sb.length();
+            String propName = "@" + ISO9075.encode(node.getProperty()).toJCRName(resolver);
 
             if (node.getOperation() == OPERATION_EQ) {
-                sb.append(" = ");
+                sb.append(propName).append(" = ");
+                appendValue(node, sb);
             } else if (node.getOperation() == OPERATION_GE) {
-                sb.append(" >= ");
+                sb.append(propName).append(" >= ");
+                appendValue(node, sb);
             } else if (node.getOperation() == OPERATION_GT) {
-                sb.append(" > ");
+                sb.append(propName).append(" > ");
+                appendValue(node, sb);
             } else if (node.getOperation() == OPERATION_LE) {
-                sb.append(" <= ");
+                sb.append(propName).append(" <= ");
+                appendValue(node, sb);
             } else if (node.getOperation() == OPERATION_LIKE) {
-                sb.insert(propIdx, XPathQueryBuilder.JCRFN_LIKE.toJCRName(resolver) + "(");
-                sb.append(",");
+                sb.append(XPathQueryBuilder.JCRFN_LIKE.toJCRName(resolver));
+                sb.append("(").append(propName).append(", ");
+                appendValue(node, sb);
+                sb.append(")");
             } else if (node.getOperation() == OPERATION_LT) {
-                sb.append(" < ");
+                sb.append(propName).append(" < ");
+                appendValue(node, sb);
             } else if (node.getOperation() == OPERATION_NE) {
-                sb.append(" != ");
+                sb.append(propName).append(" != ");
+                appendValue(node, sb);
+            } else if (node.getOperation() == OPERATION_NULL) {
+                sb.append(XPathQueryBuilder.FN_NOT.toJCRName(resolver));
+                sb.append("(").append(propName).append(")");
+            } else if (node.getOperation() == OPERATION_NOT_NULL) {
+                sb.append(propName);
             } else {
                 exceptions.add(new InvalidQueryException("Invalid operation: " + node.getOperation()));
             }
-
-            if (node.getType() == TYPE_LONG) {
-                sb.append(node.getLongValue());
-            } else if (node.getType() == TYPE_DOUBLE) {
-                sb.append(node.getDoubleValue());
-            } else if (node.getType() == TYPE_STRING) {
-                sb.append("'").append(node.getStringValue().replaceAll("'", "''")).append("'");
-            } else if (node.getType() == TYPE_DATE || node.getType() == TYPE_TIMESTAMP) {
-                Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
-                cal.setTime(node.getDateValue());
-                sb.append(XPathQueryBuilder.XS_DATETIME.toJCRName(resolver));
-                sb.append("('").append(ISO8601.format(cal)).append("')");
-            } else {
-                exceptions.add(new InvalidQueryException("Invalid type: " + node.getType()));
-            }
-
-            if (node.getOperation() == OPERATION_LIKE) {
-                sb.append(")");
-            }
         } catch (NoPrefixDeclaredException e) {
             exceptions.add(e);
         }
@@ -307,5 +301,26 @@
     public Object visit(OrderQueryNode node, Object data) {
         // @todo implement
         return data;
+    }
+
+    //----------------------------< internal >----------------------------------
+
+    private void appendValue(RelationQueryNode node, StringBuffer b)
+            throws NoPrefixDeclaredException {
+        if (node.getType() == TYPE_LONG) {
+            b.append(node.getLongValue());
+        } else if (node.getType() == TYPE_DOUBLE) {
+            b.append(node.getDoubleValue());
+        } else if (node.getType() == TYPE_STRING) {
+            b.append("'").append(node.getStringValue().replaceAll("'", "''")).append("'");
+        } else if (node.getType() == TYPE_DATE || node.getType() == TYPE_TIMESTAMP) {
+            Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+            cal.setTime(node.getDateValue());
+            b.append(XPathQueryBuilder.XS_DATETIME.toJCRName(resolver));
+            b.append("('").append(ISO8601.format(cal)).append("')");
+        } else {
+            exceptions.add(new InvalidQueryException("Invalid type: " + node.getType()));
+        }
+
     }
 }

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&rev=126276&p1=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/xpath/XPathQueryBuilder.java&r1=126275&p2=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/search/xpath/XPathQueryBuilder.java&r2=126276
==============================================================================
--- 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 Jan 24 02:20:40 2005
@@ -203,8 +203,27 @@
                 break;
             case XPathTreeConstants.JJTSTEPEXPR:
                 if (isAttributeAxis(node)) {
-                    // traverse
-                    node.childrenAccept(this, data);
+                    if (data instanceof RelationQueryNode) {
+                        // traverse
+                        node.childrenAccept(this, data);
+                    } else if (data instanceof NotQueryNode) {
+                        // is null expression
+                        RelationQueryNode isNull
+                                = new RelationQueryNode((QueryNode) data,
+                                        RelationQueryNode.OPERATION_NULL);
+                        node.childrenAccept(this, isNull);
+                        NotQueryNode notNode = (NotQueryNode) data;
+                        NAryQueryNode parent = (NAryQueryNode) notNode.getParent();
+                        parent.removeOperand(notNode);
+                        parent.addOperand(isNull);
+                    } else {
+                        // not null expression
+                        RelationQueryNode notNull
+                                = new RelationQueryNode((QueryNode) data,
+                                        RelationQueryNode.OPERATION_NOT_NULL);
+                        node.childrenAccept(this, notNull);
+                        ((NAryQueryNode) data).addOperand(notNull);
+                    }
                 } else {
                     if (data instanceof PathQueryNode) {
                         data = createLocationStep(node, (PathQueryNode) data);

Modified: incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/search/SimpleQueryTest.java
Url: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/search/SimpleQueryTest.java?view=diff&rev=126276&p1=incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/search/SimpleQueryTest.java&r1=126275&p2=incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/search/SimpleQueryTest.java&r2=126276
==============================================================================
--- incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/search/SimpleQueryTest.java	(original)
+++ incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/search/SimpleQueryTest.java	Mon Jan 24 02:20:40 2005
@@ -296,4 +296,44 @@
 
     }
 
+    public void testIsNull() throws Exception {
+        Node foo = testRootNode.addNode("foo");
+        foo.setProperty("mytext", "the quick brown fox jumps over the lazy dog.");
+        Node bar = testRootNode.addNode("bar");
+        bar.setProperty("text", "the quick brown fox jumps over the lazy dog.");
+
+        testRootNode.save();
+
+        String sql = "SELECT * FROM nt:unstructured WHERE mytext is null and jcr:path LIKE '/"
+                + testRoot + "/%'";
+        Query q = superuser.getWorkspace().getQueryManager().createQuery(sql, "sql");
+        QueryResult result = q.execute();
+        checkResult(result, 1);
+
+        String xpath = "/" + testRoot + "/*[@jcr:primaryType='nt:unstructured' and fn:not(@mytext)]";
+        q = superuser.getWorkspace().getQueryManager().createQuery(xpath, Query.XPATH_DOCUMENT_VIEW);
+        result = q.execute();
+        checkResult(result, 1);
+
+    }
+
+    public void testIsNotNull() throws Exception {
+        Node foo = testRootNode.addNode("foo");
+        foo.setProperty("mytext", "the quick brown fox jumps over the lazy dog.");
+        Node bar = testRootNode.addNode("bar");
+        bar.setProperty("text", "the quick brown fox jumps over the lazy dog.");
+
+        testRootNode.save();
+
+        String sql = "SELECT * FROM nt:unstructured WHERE mytext is not null";
+        Query q = superuser.getWorkspace().getQueryManager().createQuery(sql, "sql");
+        QueryResult result = q.execute();
+        checkResult(result, 1);
+
+        String xpath = "//*[@jcr:primaryType='nt:unstructured' and @mytext]";
+        q = superuser.getWorkspace().getQueryManager().createQuery(xpath, Query.XPATH_DOCUMENT_VIEW);
+        result = q.execute();
+        checkResult(result, 1);
+    }
+
 }