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/09/07 15:14:41 UTC

svn commit: r1382008 [1/2] - in /jackrabbit/oak/trunk/oak-core/src: main/java/org/apache/jackrabbit/oak/query/ main/java/org/apache/jackrabbit/oak/query/ast/ test/java/org/apache/jackrabbit/oak/query/ test/resources/org/apache/jackrabbit/oak/query/

Author: thomasm
Date: Fri Sep  7 13:14:40 2012
New Revision: 1382008

URL: http://svn.apache.org/viewvc?rev=1382008&view=rev
Log:
OAK-28 Query implementation: new feature "measure" to get the number of scanned nodes per selector instead of the nodes themselves or the query plan; (join) conditions are pushed down to the selectors if possible, this can reduce the number of nodes read; renamed method "apply" to "restrict"; the XPath to SQL-2 converter now supports joins and only uses parentheses where needed; test queries are now much faster because /jcr:system child nodes are excluded and join conditions are pushed down; if queries take more than 3 seconds now the test is considered as failed

Modified:
    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/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/AndImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeJoinConditionImpl.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/ConstraintImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DescendantNodeImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DescendantNodeJoinConditionImpl.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/FullTextSearchScoreImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/JoinConditionImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/JoinImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LengthImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LowerCaseImpl.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/NotImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/OrImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyExistenceImpl.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/SameNodeImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SameNodeJoinConditionImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SourceImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/UpperCaseImpl.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/QueryTest.java
    jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/sql2.txt
    jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/sql2_explain.txt
    jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/xpath.txt

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=1382008&r1=1382007&r2=1382008&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 Fri Sep  7 13:14:40 2012
@@ -74,7 +74,7 @@ public class Query {
     private QueryEngineImpl queryEngine;
     private final OrderingImpl[] orderings;
     private ColumnImpl[] columns;
-    private boolean explain;
+    private boolean explain, measure;
     private long limit = Long.MAX_VALUE;
     private long offset;
     private long size = -1;
@@ -244,8 +244,8 @@ public class Query {
             }
 
         }.visit(this);
-        source.init(this);
         source.setQueryConstraint(constraint);
+        source.init(this);
         for (ColumnImpl column : columns) {
             column.bindSelector(source);
         }
@@ -291,6 +291,10 @@ public class Query {
         this.explain = explain;
     }
 
+    public void setMeasure(boolean measure) {
+        this.measure = measure;
+    }
+
     public ResultImpl executeQuery(String revisionId, NodeState root) {
         return new ResultImpl(this, revisionId, root);
     }
@@ -301,10 +305,14 @@ public class Query {
         if (explain) {
             String plan = source.getPlan();
             columns = new ColumnImpl[] { new ColumnImpl("explain", "plan", "plan")};
-            ResultRowImpl r = new ResultRowImpl(this, new String[0], new CoreValue[] { getValueFactory().createValue(plan) }, null);
+            ResultRowImpl r = new ResultRowImpl(this,
+                    new String[0],
+                    new CoreValue[] { getValueFactory().createValue(plan) },
+                    null);
             it = Arrays.asList(r).iterator();
         } else {
             it = new RowIterator(revisionId, root, limit, offset);
+            long resultCount = 0;
             if (orderings != null) {
                 // TODO "order by" is not necessary if the used index returns
                 // rows in the same order
@@ -313,9 +321,40 @@ public class Query {
                     ResultRowImpl r = it.next();
                     list.add(r);
                 }
-                size = list.size();
+                resultCount = size = list.size();
                 Collections.sort(list);
                 it = list.iterator();
+            } else if (measure) {
+                while (it.hasNext()) {
+                    resultCount++;
+                    it.next();
+                }
+            }
+            if (measure) {
+                columns = new ColumnImpl[] {
+                        new ColumnImpl("measure", "selector", "selector"),
+                        new ColumnImpl("measure", "scanCount", "scanCount")
+                };
+                ArrayList<ResultRowImpl> list = new ArrayList<ResultRowImpl>();
+                ResultRowImpl r = new ResultRowImpl(this,
+                        new String[0],
+                        new CoreValue[] {
+                            getValueFactory().createValue("query"),
+                            getValueFactory().createValue(resultCount),
+                            },
+                        null);
+                list.add(r);
+                for (SelectorImpl selector : selectors) {
+                    r = new ResultRowImpl(this,
+                            new String[0],
+                            new CoreValue[] {
+                                getValueFactory().createValue(selector.getSelectorName()),
+                                getValueFactory().createValue(selector.getScanCount()),
+                                },
+                            null);
+                    list.add(r);
+                }
+                it = list.iterator();
             }
         }
         return it;
@@ -458,6 +497,7 @@ public class Query {
         if (prepared) {
             return;
         }
+        prepared = true;
         source.prepare(mk);
     }
 

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=1382008&r1=1382007&r2=1382008&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 Fri Sep  7 13:14:40 2012
@@ -109,7 +109,12 @@ public class SQL2Parser {
         expected = new ArrayList<String>();
         bindVariables = new HashMap<String, BindVariableValueImpl>();
         read();
-        boolean explain = readIf("EXPLAIN");
+        boolean explain = false, measure = false;
+        if (readIf("EXPLAIN")) {
+            explain = true;
+        } else if (readIf("MEASURE")) {
+            measure = true;
+        }
         read("SELECT");
         ArrayList<ColumnOrWildcard> list = parseColumns();
         if (supportSQL1) {
@@ -133,6 +138,7 @@ public class SQL2Parser {
         }
         Query q = new Query(source, constraint, orderings, columnArray, valueFactory);
         q.setExplain(explain);
+        q.setMeasure(measure);
         try {
             q.init();
         } catch (Exception e) {

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=1382008&r1=1382007&r2=1382008&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 Fri Sep  7 13:14:40 2012
@@ -62,6 +62,10 @@ public class XPathToSQL2Converter {
         if (explain) {
             query = query.substring("explain ".length());
         }
+        boolean measure = query.startsWith("measure");
+        if (measure) {
+            query = query.substring("measure ".length());
+        }
         // TODO verify this is correct
         if (!query.startsWith("/")) {
             query = "/jcr:root/" + query;
@@ -70,150 +74,141 @@ public class XPathToSQL2Converter {
         expected = new ArrayList<String>();
         read();
 
-        String path = "";
-        String nodeName = null;
-        Expression condition = null;
-
         currentSelector.name = "a";
         currentSelector.nodeType = "nt:base";
-        selectors.add(currentSelector);
 
         ArrayList<Expression> columnList = new ArrayList<Expression>();
 
-        // queries of the type
-        // jcr:root/*
-        // jcr:root/test/*
-        // jcr:root/element()
-        // jcr:root/element(*)
-        boolean children = false;
-
-        // queries of the type
-        // /jcr:root//...
-        // /jcr:root/test//...
-        // /jcr:root[...]
-        // /jcr:root (just by itself)
-        boolean descendants = false;
-
         // TODO support one node matcher ('*' wildcard), example:
         // /jcr:root/content/acme/*/jcr:content[@template='/apps/acme']
 
         // TODO verify '//' is behaving correctly, as specified, example:
         // /jcr:root/content/acme//jcr:content[@template='/apps/acme']
+        
+        String pathPattern = "";
 
         while (true) {
-            if (readIf("/")) {
-                if (nodeName != null) {
-                    throw getSyntaxError("non-path condition");
+            boolean shortcut = false;
+            boolean slash = readIf("/");
+            if (!slash) {
+                break;
+            } else if (readIf("jcr:root")) {
+                // "/jcr:root" may only appear at the beginning
+                if (!pathPattern.isEmpty()) {
+                    throw getSyntaxError("jcr:root needs to be at the beginning");
                 }
                 if (readIf("/")) {
-                    descendants = true;
-                } else if (readIf("jcr:root")) {
-                    path = "/";
+                    // "/jcr:root/"
+                    currentSelector.path = "/";
+                    pathPattern = "/";
                     if (readIf("/")) {
-                        if (readIf("/")) {
-                            descendants = true;
-                        }
-                    } else {
-                        descendants = true;
-                        if (readIf("[")) {
-                            Expression c = parseConstraint();
-                            condition = add(condition, c);
-                            read("]");
-                        }
-                        // expected end of statement
-                        break;
+                        // "/jcr:root//"
+                        pathPattern = "//";
+                        currentSelector.isDescendant = true;
                     }
                 } else {
-                    descendants = false;
+                    // for example "/jcr:root[condition]"
+                    pathPattern = "/%";
+                    currentSelector.path = "/";
+                    currentSelector.isDescendant = true;
+                    shortcut = true;
                 }
-                if (readIf("*")) {
-                    currentSelector.nodeType = "nt:base";
-                    children = true;
-                } else if (readIf("text")) {
-                    read("(");
-                    read(")");
-                    if (descendants) {
-                        nodeName = "jcr:xmltext";
-                    } else {
-                        path = PathUtils.concat(path, "jcr:xmltext");
-                    }
-                } else if (readIf("element")) {
-                    read("(");
-                    if (readIf(")")) {
+            } else if (readIf("/")) {
+                // "//" was read
+                pathPattern += "%";
+                currentSelector.isDescendant = true;
+            } else {
+                // the token "/" was read
+                pathPattern += "/";
+            }
+            if (shortcut) {
+                // query of the style: "/jcr:root[condition]"
+            } else if (readIf("*")) {
+                // "...*"
+                pathPattern += "%";
+                currentSelector.nodeType = "nt:base";
+                currentSelector.isChild = true;
+            } else if (readIf("text")) {
+                // "...text()"
+                pathPattern += "jcr:xmltext";
+                read("(");
+                read(")");
+                if (currentSelector.isDescendant) {
+                    currentSelector.nodeName = "jcr:xmltext";
+                } else {
+                    currentSelector.path = PathUtils.concat(currentSelector.path, "jcr:xmltext");
+                }
+            } else if (readIf("element")) {
+                // "...element(..."
+                read("(");
+                if (readIf(")")) {
+                    // any
+                    currentSelector.isChild = true;
+                } else {
+                    if (readIf("*")) {
                         // any
-                        children = true;
+                        currentSelector.isChild = true;
+                        pathPattern += "%";
                     } else {
-                        if (readIf("*")) {
-                            // any
-                            children = true;
-                        } else {
-                            children = false;
-                            String name = readIdentifier();
-                            path = PathUtils.concat(path, name);
-                        }
-                        if (readIf(",")) {
-                            currentSelector.nodeType = readIdentifier();
-                        } else {
-                            currentSelector.nodeType = "nt:base";
-                        }
-                        read(")");
+                        currentSelector.isChild = false;
+                        String name = readIdentifier();
+                        pathPattern += name;
+                        currentSelector.path = PathUtils.concat(currentSelector.path, name);
                     }
-                } else if (readIf("@")) {
-                    Property p = readProperty();
-                    columnList.add(p);
-                } else if (readIf("(")) {
-                    do {
-                        read("@");
-                        Property p = readProperty();
-                        columnList.add(p);
-                    } while (readIf("|"));
-                    read(")");
-                } else {
-                    if (descendants) {
-                        nodeName = readIdentifier();
+                    if (readIf(",")) {
+                        currentSelector.nodeType = readIdentifier();
                     } else {
-                        String name = readIdentifier();
-                        path = PathUtils.concat(path, name);
+                        currentSelector.nodeType = "nt:base";
                     }
+                    read(")");
                 }
-                if (readIf("[")) {
-                    Expression c = parseConstraint();
-                    condition = add(condition, c);
-                    read("]");
+            } else if (readIf("@")) {
+                Property p = readProperty();
+                columnList.add(p);
+            } else if (readIf("(")) {
+                // special case: ".../(@prop)" is actually not a child node, 
+                // but the same node (selector) as before 
+                if (selectors.size() > 0) {
+                    currentSelector = selectors.remove(selectors.size() - 1);
+                    // prevent (join) conditions are added again
+                    currentSelector.isChild = false;
+                    currentSelector.isDescendant = false;
+                    currentSelector.path = "";
+                    currentSelector.nodeName = null;
                 }
+                if (currentSelector.nodeType == null) {
+                    currentSelector.nodeType = "nt:base";
+                }
+                do {
+                    read("@");
+                    Property p = readProperty();
+                    columnList.add(p);
+                } while (readIf("|"));
+                read(")");
             } else {
-                break;
-            }
-        }
-        if (path.isEmpty()) {
-            // no condition
-        } else {
-            if (!PathUtils.isAbsolute(path)) {
-                path = PathUtils.concat("/", path);
-            }
-            if (descendants) {
-                Function c = new Function("isdescendantnode");
-                c.params.add(new SelectorExpr(currentSelector));
-                c.params.add(Literal.newString(path));
-                condition = add(condition, c);
-            } else if (children) {
-                Function c = new Function("ischildnode");
-                c.params.add(new SelectorExpr(currentSelector));
-                c.params.add(Literal.newString(path));
-                condition = add(condition, c);
-            } else {
-                Function c = new Function("issamenode");
-                c.params.add(new SelectorExpr(currentSelector));
-                c.params.add(Literal.newString(path));
-                condition = add(condition, c);
-            }
-            if (nodeName != null) {
-                Function f = new Function("name");
-                f.params.add(new SelectorExpr(currentSelector));
-                Condition c = new Condition(f, "=", Literal.newString(nodeName));
-                condition = add(condition, c);
+                // path restriction
+                String name = readIdentifier();
+                if (currentSelector.isDescendant) {
+                    pathPattern += name;
+                    currentSelector.nodeName = name;
+                } else {
+                    pathPattern += name;
+                    currentSelector.path = PathUtils.concat(currentSelector.path, name);
+                }
             }
-        }
+            if (readIf("[")) {
+                Expression c = parseConstraint();
+                currentSelector.condition = add(currentSelector.condition, c);
+                read("]");
+            }
+            nextSelector(false);
+        }
+        if (selectors.size() == 0) {
+            nextSelector(true);
+        }
+        // the current selector wasn't used so far
+        // go back to the last one
+        currentSelector = selectors.get(selectors.size() - 1);
         if (selectors.size() == 1) {
             currentSelector.onlySelector = true;
         }
@@ -235,9 +230,15 @@ public class XPathToSQL2Converter {
             throw getSyntaxError("<end>");
         }
         StringBuilder buff = new StringBuilder();
+        
+        // explain | measure ...
         if (explain) {
             buff.append("explain ");
+        } else if (measure) {
+            buff.append("measure ");
         }
+        
+        // select ...
         buff.append("select ");
         buff.append(new Property(currentSelector, "jcr:path").toString());
         buff.append(", ");
@@ -251,6 +252,8 @@ public class XPathToSQL2Converter {
                 buff.append(columnList.get(i).toString());
             }
         }
+        
+        // from ...
         buff.append(" from ");
         for (int i = 0; i < selectors.size(); i++) {
             Selector s = selectors.get(i);
@@ -262,9 +265,23 @@ public class XPathToSQL2Converter {
                 buff.append(" on ").append(s.joinCondition);
             }
         }
-        if (condition != null) {
-            buff.append(" where ").append(removeParens(condition.toString()));
+        
+        // where ...
+        StringBuilder condition = new StringBuilder();
+        for (int i = 0; i < selectors.size(); i++) {
+            Selector s = selectors.get(i);
+            if (s.condition != null) {
+                if (condition.length() > 0) {
+                    condition.append(" and ");
+                }
+                condition.append(s.condition);
+            }
+        }
+        if (condition.length() > 0) {
+            buff.append(" where ").append(condition.toString());
         }
+        
+        // order by ...
         if (!orderList.isEmpty()) {
             buff.append(" order by ");
             for (int i = 0; i < orderList.size(); i++) {
@@ -276,18 +293,90 @@ public class XPathToSQL2Converter {
         }
         return buff.toString();
     }
+    
+    private void nextSelector(boolean force) throws ParseException {
+        boolean isFirstSelector = selectors.size() == 0;
+        String path = currentSelector.path;
+        Expression condition = currentSelector.condition;
+        Expression joinCondition = currentSelector.joinCondition;
+        if (currentSelector.nodeName != null) {
+            Function f = new Function("name");
+            f.params.add(new SelectorExpr(currentSelector));
+            Condition c = new Condition(f, "=", 
+                    Literal.newString(currentSelector.nodeName), 
+                    Expression.PRECEDENCE_CONDITION);
+            condition = add(condition, c);
+        }
+        if (currentSelector.isDescendant) {
+            if (isFirstSelector) {
+                if (!path.isEmpty()) {
+                    if (!PathUtils.isAbsolute(path)) {
+                        path = PathUtils.concat("/", path);
+                    }
+                    Function c = new Function("isdescendantnode");
+                    c.params.add(new SelectorExpr(currentSelector));
+                    c.params.add(Literal.newString(path));
+                    condition = add(condition, c);
+                }
+            } else {
+                Function c = new Function("isdescendantnode");
+                c.params.add(new SelectorExpr(currentSelector));
+                c.params.add(new SelectorExpr(selectors.get(selectors.size() - 1)));
+                joinCondition = c;
+            } 
+        } else if (currentSelector.isChild) {
+            if (isFirstSelector) {
+                if (!path.isEmpty()) {
+                    if (!PathUtils.isAbsolute(path)) {
+                        path = PathUtils.concat("/", path);
+                    }
+                    Function c = new Function("ischildnode");
+                    c.params.add(new SelectorExpr(currentSelector));
+                    c.params.add(Literal.newString(path));
+                    condition = add(condition, c);
+                }
+            } else {
+                Function c = new Function("ischildnode");
+                c.params.add(new SelectorExpr(currentSelector));
+                c.params.add(new SelectorExpr(selectors.get(selectors.size() - 1)));
+                joinCondition = c;
+            }
+        } else {
+            if (!force && condition == null && joinCondition == null) {
+                // a child node of a given path, such as "/test"
+                // use the same selector for now, and extend the path
+            } else if (PathUtils.isAbsolute(path)) {
+                Function c = new Function("issamenode");
+                c.params.add(new SelectorExpr(currentSelector));
+                c.params.add(Literal.newString(path));
+                condition = add(condition, c);
+            }
+        }
+        if (force || condition != null || joinCondition != null) {
+            String nextSelectorName = "" + (char) (currentSelector.name.charAt(0) + 1);
+            if (nextSelectorName.compareTo("x") > 0) {
+                throw getSyntaxError("too many joins");
+            }
+            Selector nextSelector = new Selector();
+            nextSelector.name = nextSelectorName;
+            currentSelector.condition = condition;
+            currentSelector.joinCondition = joinCondition;
+            selectors.add(currentSelector);
+            currentSelector = nextSelector;
+        }
+    }
 
     private static Expression add(Expression old, Expression add) {
         if (old == null) {
             return add;
         }
-        return new Condition(old, "and", add);
+        return new Condition(old, "and", add, Expression.PRECEDENCE_AND);
     }
 
     private Expression parseConstraint() throws ParseException {
         Expression a = parseAnd();
         while (readIf("or")) {
-            a = new Condition(a, "or", parseAnd());
+            a = new Condition(a, "or", parseAnd(), Expression.PRECEDENCE_OR);
         }
         return a;
     }
@@ -295,7 +384,7 @@ public class XPathToSQL2Converter {
     private Expression parseAnd() throws ParseException {
         Expression a = parseCondition();
         while (readIf("and")) {
-            a = new Condition(a, "and", parseCondition());
+            a = new Condition(a, "and", parseCondition(), Expression.PRECEDENCE_AND);
         }
         return a;
     }
@@ -308,7 +397,7 @@ public class XPathToSQL2Converter {
             if (a instanceof Condition && ((Condition) a).operator.equals("is not null")) {
                 // not(@property) -> @property is null
                 Condition c = (Condition) a;
-                c = new Condition(c.left, "is null", null);
+                c = new Condition(c.left, "is null", null, Expression.PRECEDENCE_CONDITION);
                 a = c;
             } else {
                 Function f = new Function("not");
@@ -332,21 +421,21 @@ public class XPathToSQL2Converter {
     private Condition parseCondition(Expression left) throws ParseException {
         Condition c;
         if (readIf("=")) {
-            c = new Condition(left, "=", parseExpression());
+            c = new Condition(left, "=", parseExpression(), Expression.PRECEDENCE_CONDITION);
         } else if (readIf("<>")) {
-            c = new Condition(left, "<>", parseExpression());
+            c = new Condition(left, "<>", parseExpression(), Expression.PRECEDENCE_CONDITION);
         } else if (readIf("!=")) {
-            c = new Condition(left, "<>", parseExpression());
+            c = new Condition(left, "<>", parseExpression(), Expression.PRECEDENCE_CONDITION);
         } else if (readIf("<")) {
-            c = new Condition(left, "<", parseExpression());
+            c = new Condition(left, "<", parseExpression(), Expression.PRECEDENCE_CONDITION);
         } else if (readIf(">")) {
-            c = new Condition(left, ">", parseExpression());
+            c = new Condition(left, ">", parseExpression(), Expression.PRECEDENCE_CONDITION);
         } else if (readIf("<=")) {
-            c = new Condition(left, "<=", parseExpression());
+            c = new Condition(left, "<=", parseExpression(), Expression.PRECEDENCE_CONDITION);
         } else if (readIf(">=")) {
-            c = new Condition(left, ">=", parseExpression());
+            c = new Condition(left, ">=", parseExpression(), Expression.PRECEDENCE_CONDITION);
         } else {
-            c = new Condition(left, "is not null", null);
+            c = new Condition(left, "is not null", null, Expression.PRECEDENCE_CONDITION);
         }
         return c;
     }
@@ -435,7 +524,7 @@ public class XPathToSQL2Converter {
 
     private Expression parseFunction(String functionName) throws ParseException {
         if ("jcr:like".equals(functionName)) {
-            Condition c = new Condition(parseExpression(), "like", null);
+            Condition c = new Condition(parseExpression(), "like", null, Expression.PRECEDENCE_CONDITION);
             read(",");
             c.right = parseExpression();
             read(")");
@@ -817,33 +906,95 @@ public class XPathToSQL2Converter {
         return new ParseException("Query:\n" + query, index);
     }
 
-    static String removeParens(String s) {
-        if (s.startsWith("(") && s.endsWith(")")) {
-            return s.substring(1, s.length() - 1);
-        }
-        return s;
-    }
-
     /**
      * A selector.
      */
     static class Selector {
 
+        /**
+         * The selector name.
+         */
         String name;
+        
+        /**
+         * Whether this is the only selector in the query.
+         */
         boolean onlySelector;
+        
+        /**
+         * The node type, if set, or null.
+         */
         String nodeType;
-        Expression joinCondition;
+        
+        /**
+         * Whether this is a child node of the previous selector or a given path.
+         */
+        // queries of the type
+        // jcr:root/*
+        // jcr:root/test/*
+        // jcr:root/element()
+        // jcr:root/element(*)
+        boolean isChild;
 
+        /**
+         * Whether this is a descendant of the previous selector or a given path.
+         */
+        // queries of the type
+        // /jcr:root//...
+        // /jcr:root/test//...
+        // /jcr:root[...]
+        // /jcr:root (just by itself)
+        boolean isDescendant;
+        
+        /**
+         * The path (only used for the first selector).
+         */
+        String path = "";
+        
+        /**
+         * The node name, if set.
+         */
+        String nodeName;
+        
+        /**
+         * The condition for this selector.
+         */
+        Expression condition;
+        
+        /**
+         * The join condition from the previous selector.
+         */
+        Expression joinCondition;
+        
     }
 
     /**
      * An expression.
      */
     abstract static class Expression {
-
+        
+        static final int PRECEDENCE_OR = 1, PRECEDENCE_AND = 2, 
+                PRECEDENCE_CONDITION = 3, PRECEDENCE_OPERAND = 4;
+        
+        /**
+         * Whether this is a condition.
+         * 
+         * @return true if it is 
+         */
         boolean isCondition() {
             return false;
         }
+        
+        /**
+         * Get the operator / operation precedence. The JCR specification uses:
+         * 1=OR, 2=AND, 3=condition, 4=operand  
+         * 
+         * @return the precedence (as an example, multiplication needs to return
+         *         a higher number than addition)
+         */
+        int getPrecedence() {
+            return PRECEDENCE_OPERAND;
+        }
 
     }
 
@@ -932,21 +1083,49 @@ public class XPathToSQL2Converter {
         final Expression left;
         final String operator;
         Expression right;
+        final int precedence;
 
-        Condition(Expression left, String operator, Expression right) {
+        /**
+         * Create a new condition.
+         * 
+         * @param left the left hand side operator, or null
+         * @param operator the operator
+         * @param right the right hand side operator, or null
+         * @param precedence the operator precedence (Expression.PRECEDENCE_...)
+         */
+        Condition(Expression left, String operator, Expression right, int precedence) {
             this.left = left;
             this.operator = operator;
             this.right = right;
+            this.precedence = precedence;
+        }
+        
+        @Override
+        int getPrecedence() {
+            return precedence;
         }
 
         @Override
         public String toString() {
-            return
-                "(" +
-                (left == null ? "" : left + " ") +
-                operator +
-                (right == null ? "" : " " + right) +
-                ")";
+            StringBuilder buff = new StringBuilder();
+            if (left != null) {
+                if (left.getPrecedence() < precedence) {
+                    buff.append('(').append(left.toString()).append(')');
+                } else {
+                    buff.append(left.toString());
+                }
+                buff.append(' ');
+            }
+            buff.append(operator);
+            if (right != null) {
+                buff.append(' ');
+                if (right.getPrecedence() < precedence) {
+                    buff.append('(').append(right.toString()).append(')');
+                } else {
+                    buff.append(right.toString());
+                }
+            }
+            return buff.toString();
         }
 
         @Override
@@ -976,7 +1155,7 @@ public class XPathToSQL2Converter {
                 if (i > 0) {
                     buff.append(", ");
                 }
-                buff.append(removeParens(params.get(i).toString()));
+                buff.append(params.get(i).toString());
             }
             buff.append(')');
             return buff.toString();
@@ -1005,7 +1184,7 @@ public class XPathToSQL2Converter {
         @Override
         public String toString() {
             StringBuilder buff = new StringBuilder("cast(");
-            buff.append(removeParens(expr.toString()));
+            buff.append(expr.toString());
             buff.append(" as ").append(type).append(')');
             return buff.toString();
         }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AndImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AndImpl.java?rev=1382008&r1=1382007&r2=1382008&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AndImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AndImpl.java Fri Sep  7 13:14:40 2012
@@ -56,9 +56,15 @@ public class AndImpl extends ConstraintI
     }
 
     @Override
-    public void apply(FilterImpl f) {
-        constraint1.apply(f);
-        constraint2.apply(f);
+    public void restrict(FilterImpl f) {
+        constraint1.restrict(f);
+        constraint2.restrict(f);
+    }
+
+    @Override
+    public void restrictPushDown(SelectorImpl s) {
+        constraint1.restrictPushDown(s);
+        constraint2.restrictPushDown(s);
     }
 
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeImpl.java?rev=1382008&r1=1382007&r2=1382008&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeImpl.java Fri Sep  7 13:14:40 2012
@@ -72,11 +72,18 @@ public class ChildNodeImpl extends Const
     }
 
     @Override
-    public void apply(FilterImpl f) {
+    public void restrict(FilterImpl f) {
         if (selector == f.getSelector()) {
             String path = getAbsolutePath(parentPath);
             f.restrictPath(path, Filter.PathRestriction.DIRECT_CHILDREN);
         }
     }
 
+    @Override
+    public void restrictPushDown(SelectorImpl s) {
+        if (s == selector) {
+            s.restrictSelector(this);
+        }
+    }
+
 }
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeJoinConditionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeJoinConditionImpl.java?rev=1382008&r1=1382007&r2=1382008&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeJoinConditionImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeJoinConditionImpl.java Fri Sep  7 13:14:40 2012
@@ -72,7 +72,7 @@ public class ChildNodeJoinConditionImpl 
     }
 
     @Override
-    public void apply(FilterImpl f) {
+    public void restrict(FilterImpl f) {
         String p = parentSelector.currentPath();
         String c = childSelector.currentPath();
         if (f.getSelector() == parentSelector && c != null) {
@@ -83,4 +83,9 @@ public class ChildNodeJoinConditionImpl 
         }
     }
 
+    @Override
+    public void restrictPushDown(SelectorImpl s) {
+        // nothing to do
+    }
+
 }
\ No newline at end of file

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=1382008&r1=1382007&r2=1382008&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 Fri Sep  7 13:14:40 2012
@@ -121,7 +121,7 @@ public class ComparisonImpl extends Cons
     }
 
     @Override
-    public void apply(FilterImpl f) {
+    public void restrict(FilterImpl f) {
         CoreValue v = operand2.currentValue();
         if (v != null) {
             if (operator == Operator.LIKE) {
@@ -147,6 +147,16 @@ public class ComparisonImpl extends Cons
         }
     }
 
+    @Override
+    public void restrictPushDown(SelectorImpl s) {
+        CoreValue v = operand2.currentValue();
+        if (v != null) {
+            if (operand1.canRestrictSelector(s)) {
+                s.restrictSelector(this);
+            }
+        }
+    }
+
     /**
      * A pattern matcher.
      */

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ConstraintImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ConstraintImpl.java?rev=1382008&r1=1382007&r2=1382008&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ConstraintImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ConstraintImpl.java Fri Sep  7 13:14:40 2012
@@ -37,6 +37,14 @@ public abstract class ConstraintImpl ext
      *
      * @param f the filter
      */
-    public abstract void apply(FilterImpl f);
+    public abstract void restrict(FilterImpl f);
+
+    /**
+     * Push as much of the condition down to this selector, further restricting
+     * the selector condition if possible.
+     *
+     * @param s the selector
+     */
+    public abstract void restrictPushDown(SelectorImpl s);
 
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DescendantNodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DescendantNodeImpl.java?rev=1382008&r1=1382007&r2=1382008&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DescendantNodeImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DescendantNodeImpl.java Fri Sep  7 13:14:40 2012
@@ -48,6 +48,9 @@ public class DescendantNodeImpl extends 
     public boolean evaluate() {
         String p = selector.currentPath();
         String path = getAbsolutePath(ancestorPath);
+        if (p == null || path == null) {
+            return false;
+        }
         return PathUtils.isAncestor(path, p);
     }
 
@@ -66,11 +69,18 @@ public class DescendantNodeImpl extends 
     }
 
     @Override
-    public void apply(FilterImpl f) {
+    public void restrict(FilterImpl f) {
         if (f.getSelector() == selector) {
             String path = getAbsolutePath(ancestorPath);
             f.restrictPath(path, Filter.PathRestriction.ALL_CHILDREN);
         }
     }
 
+    @Override
+    public void restrictPushDown(SelectorImpl s) {
+        if (s == selector) {
+            s.restrictSelector(this);
+        }
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DescendantNodeJoinConditionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DescendantNodeJoinConditionImpl.java?rev=1382008&r1=1382007&r2=1382008&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DescendantNodeJoinConditionImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DescendantNodeJoinConditionImpl.java Fri Sep  7 13:14:40 2012
@@ -71,7 +71,7 @@ public class DescendantNodeJoinCondition
     }
 
     @Override
-    public void apply(FilterImpl f) {
+    public void restrict(FilterImpl f) {
         String d = descendantSelector.currentPath();
         String a = ancestorSelector.currentPath();
         if (d != null && f.getSelector() == ancestorSelector) {
@@ -82,4 +82,9 @@ public class DescendantNodeJoinCondition
         }
     }
 
+    @Override
+    public void restrictPushDown(SelectorImpl s) {
+        // nothing to do
+    }
+
 }

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=1382008&r1=1382007&r2=1382008&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 Fri Sep  7 13:14:40 2012
@@ -29,6 +29,17 @@ public abstract class DynamicOperandImpl
 
     public abstract void apply(FilterImpl f, Operator operator, CoreValue v);
 
+    /**
+     * Check whether the condition can be applied to a selector (to restrict the
+     * selector). The method may return true if the operand can be evaluated
+     * when the given selector and all previous selectors in the join can be
+     * evaluated.
+     *
+     * @param s the selector
+     * @return true if the condition can be applied
+     */
+    public abstract boolean canRestrictSelector(SelectorImpl s);
+
     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=1382008&r1=1382007&r2=1382008&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 Fri Sep  7 13:14:40 2012
@@ -120,7 +120,7 @@ public class EquiJoinConditionImpl exten
     }
 
     @Override
-    public void apply(FilterImpl f) {
+    public void restrict(FilterImpl f) {
         PropertyState p1 = selector1.currentProperty(property1Name);
         PropertyState p2 = selector2.currentProperty(property2Name);
         if (f.getSelector() == selector1 && p2 != null) {
@@ -137,4 +137,18 @@ public class EquiJoinConditionImpl exten
         }
     }
 
+    @Override
+    public void restrictPushDown(SelectorImpl s) {
+        // both properties may not be null
+        if (s == selector1) {
+            PropertyExistenceImpl ex = new PropertyExistenceImpl(s.getSelectorName(), property1Name);
+            ex.bindSelector(s);
+            s.restrictSelector(ex);
+        } else if (s == selector2) {
+            PropertyExistenceImpl ex = new PropertyExistenceImpl(s.getSelectorName(), property2Name);
+            ex.bindSelector(s);
+            s.restrictSelector(ex);
+        }
+    }
+
 }

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=1382008&r1=1382007&r2=1382008&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 Fri Sep  7 13:14:40 2012
@@ -123,7 +123,7 @@ public class FullTextSearchImpl extends 
     }
 
     @Override
-    public void apply(FilterImpl f) {
+    public void restrict(FilterImpl f) {
         if (propertyName != null) {
             if (f.getSelector() == selector) {
                 f.restrictProperty(propertyName, Operator.NOT_EQUAL, (CoreValue) null);
@@ -132,6 +132,13 @@ public class FullTextSearchImpl extends 
         f.restrictFulltextCondition(fullTextSearchExpression.currentValue().getString());
     }
 
+    @Override
+    public void restrictPushDown(SelectorImpl s) {
+        if (s == selector) {
+            selector.restrictSelector(this);
+        }
+    }
+
     /**
      * A parser for fulltext condition literals. The grammar is defined in the
      * <a href="http://www.day.com/specs/jcr/2.0/6_Query.html#6.7.19">

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchScoreImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchScoreImpl.java?rev=1382008&r1=1382007&r2=1382008&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchScoreImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchScoreImpl.java Fri Sep  7 13:14:40 2012
@@ -63,4 +63,9 @@ public class FullTextSearchScoreImpl ext
         // TODO support fulltext index conditions (score)
     }
 
+    @Override
+    public boolean canRestrictSelector(SelectorImpl s) {
+        return s == selector;
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/JoinConditionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/JoinConditionImpl.java?rev=1382008&r1=1382007&r2=1382008&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/JoinConditionImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/JoinConditionImpl.java Fri Sep  7 13:14:40 2012
@@ -22,6 +22,8 @@ public abstract class JoinConditionImpl 
 
     public abstract boolean evaluate();
 
-    public abstract void apply(FilterImpl f);
+    public abstract void restrict(FilterImpl f);
+
+    public abstract void restrictPushDown(SelectorImpl selectorImpl);
 
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/JoinImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/JoinImpl.java?rev=1382008&r1=1382007&r2=1382008&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/JoinImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/JoinImpl.java Fri Sep  7 13:14:40 2012
@@ -79,36 +79,31 @@ public class JoinImpl extends SourceImpl
     public void init(Query query) {
         switch (joinType) {
         case INNER:
-            left.setQueryConstraint(queryConstraint);
-            right.setQueryConstraint(queryConstraint);
-            right.setJoinCondition(joinCondition);
-            left.init(query);
-            right.init(query);
+            left.addJoinCondition(joinCondition, false);
+            right.addJoinCondition(joinCondition, true);
             break;
         case LEFT_OUTER:
-            left.setQueryConstraint(queryConstraint);
             right.setOuterJoin(true);
-            right.setQueryConstraint(queryConstraint);
-            right.setJoinCondition(joinCondition);
-            left.init(query);
-            right.init(query);
+            left.addJoinCondition(joinCondition, false);
+            right.addJoinCondition(joinCondition, true);
             break;
         case RIGHT_OUTER:
-            right.setQueryConstraint(queryConstraint);
-            left.setOuterJoin(true);
-            left.setQueryConstraint(queryConstraint);
-            left.setJoinCondition(joinCondition);
-            right.init(query);
-            left.init(query);
+            // swap left and right
             // TODO right outer join: verify whether converting
             // to left outer join is always correct (given the current restrictions)
             joinType = JoinType.LEFT_OUTER;
-            // swap left and right
             SourceImpl temp = left;
             left = right;
             right = temp;
+            right.setOuterJoin(true);
+            left.addJoinCondition(joinCondition, false);
+            right.addJoinCondition(joinCondition, true);
             break;
         }
+        left.setQueryConstraint(queryConstraint);
+        right.setQueryConstraint(queryConstraint);
+        right.init(query);
+        left.init(query);
     }
 
     @Override

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LengthImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LengthImpl.java?rev=1382008&r1=1382007&r2=1382008&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LengthImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LengthImpl.java Fri Sep  7 13:14:40 2012
@@ -86,4 +86,9 @@ public class LengthImpl extends DynamicO
         // TODO LENGTH(x) conditions: can use IS NOT NULL as a condition?
     }
 
+    @Override
+    public boolean canRestrictSelector(SelectorImpl s) {
+        return propertyValue.canRestrictSelector(s);
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LowerCaseImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LowerCaseImpl.java?rev=1382008&r1=1382007&r2=1382008&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LowerCaseImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LowerCaseImpl.java Fri Sep  7 13:14:40 2012
@@ -67,4 +67,9 @@ public class LowerCaseImpl extends Dynam
         // TODO UPPER(x) conditions: can use IS NOT NULL?
     }
 
+    @Override
+    public boolean canRestrictSelector(SelectorImpl s) {
+        return operand.canRestrictSelector(s);
+    }
+
 }

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=1382008&r1=1382007&r2=1382008&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 Fri Sep  7 13:14:40 2012
@@ -72,4 +72,9 @@ public class NodeLocalNameImpl extends D
         // TODO support LOCALNAME index conditions
     }
 
+    @Override
+    public boolean canRestrictSelector(SelectorImpl s) {
+        return s == selector;
+    }
+
 }

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=1382008&r1=1382007&r2=1382008&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 Fri Sep  7 13:14:40 2012
@@ -87,6 +87,11 @@ public class NodeNameImpl extends Dynami
         // TODO support NAME(..) index conditions
     }
 
+    @Override
+    public boolean canRestrictSelector(SelectorImpl s) {
+        return s == selector;
+    }
+
     private String decodeName(String path) {
         // Name escaping (convert _x0020_ to space)
         path = ISO9075.decode(path);

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NotImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NotImpl.java?rev=1382008&r1=1382007&r2=1382008&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NotImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NotImpl.java Fri Sep  7 13:14:40 2012
@@ -51,7 +51,13 @@ public class NotImpl extends ConstraintI
     }
 
     @Override
-    public void apply(FilterImpl f) {
+    public void restrict(FilterImpl f) {
+        // ignore
+        // TODO convert NOT conditions
+    }
+
+    @Override
+    public void restrictPushDown(SelectorImpl s) {
         // ignore
         // TODO convert NOT conditions
     }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/OrImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/OrImpl.java?rev=1382008&r1=1382007&r2=1382008&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/OrImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/OrImpl.java Fri Sep  7 13:14:40 2012
@@ -57,9 +57,18 @@ public class OrImpl extends ConstraintIm
     }
 
     @Override
-    public void apply(FilterImpl f) {
+    public void restrict(FilterImpl f) {
         // ignore
         // TODO convert OR conditions to UNION
     }
 
+    @Override
+    public void restrictPushDown(SelectorImpl s) {
+        // ignore
+        // TODO some OR conditions can be applied to a selector,
+        // for example WHERE X.ID = 1 OR X.ID = 2
+        // can be applied to X as a whole,
+        // but X.ID = 1 OR Y.ID = 2 can't be applied to either
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyExistenceImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyExistenceImpl.java?rev=1382008&r1=1382007&r2=1382008&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyExistenceImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyExistenceImpl.java Fri Sep  7 13:14:40 2012
@@ -66,10 +66,17 @@ public class PropertyExistenceImpl exten
     }
 
     @Override
-    public void apply(FilterImpl f) {
+    public void restrict(FilterImpl f) {
         if (f.getSelector() == selector) {
             f.restrictProperty(propertyName, Operator.NOT_EQUAL, (CoreValue) null);
         }
     }
 
+    @Override
+    public void restrictPushDown(SelectorImpl s) {
+        if (s == selector) {
+            s.restrictSelector(this);
+        }
+    }
+
 }

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=1382008&r1=1382007&r2=1382008&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 Fri Sep  7 13:14:40 2012
@@ -165,4 +165,9 @@ public class PropertyValueImpl extends D
         }
     }
 
+    @Override
+    public boolean canRestrictSelector(SelectorImpl s) {
+        return s == selector;
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SameNodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SameNodeImpl.java?rev=1382008&r1=1382007&r2=1382008&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SameNodeImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SameNodeImpl.java Fri Sep  7 13:14:40 2012
@@ -65,7 +65,7 @@ public class SameNodeImpl extends Constr
     }
 
     @Override
-    public void apply(FilterImpl f) {
+    public void restrict(FilterImpl f) {
         if (f.getSelector() == selector) {
             String p = getAbsolutePath(path);
             f.restrictPath(p, Filter.PathRestriction.EXACT);
@@ -73,4 +73,11 @@ public class SameNodeImpl extends Constr
         // TODO validate absolute path
     }
 
+    @Override
+    public void restrictPushDown(SelectorImpl s) {
+        if (s == selector) {
+            s.restrictSelector(this);
+        }
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SameNodeJoinConditionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SameNodeJoinConditionImpl.java?rev=1382008&r1=1382007&r2=1382008&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SameNodeJoinConditionImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SameNodeJoinConditionImpl.java Fri Sep  7 13:14:40 2012
@@ -84,7 +84,7 @@ public class SameNodeJoinConditionImpl e
     }
 
     @Override
-    public void apply(FilterImpl f) {
+    public void restrict(FilterImpl f) {
         String p1 = selector1.currentPath();
         String p2 = selector2.currentPath();
         if (f.getSelector() == selector1) {
@@ -95,4 +95,9 @@ public class SameNodeJoinConditionImpl e
         }
     }
 
+    @Override
+    public void restrictPushDown(SelectorImpl s) {
+        // nothing to do
+    }
+
 }

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=1382008&r1=1382007&r2=1382008&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 Fri Sep  7 13:14:40 2012
@@ -48,6 +48,16 @@ public class SelectorImpl extends Source
 
     private final String nodeTypeName, selectorName;
     private Cursor cursor;
+    private int scanCount;
+
+    /**
+     * The selector condition can be evaluated when the given selector is
+     * evaluated. For example, for the query
+     * "select * from nt:base a inner join nt:base b where a.x = 1 and b.y = 2",
+     * the condition "a.x = 1" can be evaluated when evaluating selector a. The
+     * other part of the condition can't be evaluated until b is available.
+     */
+    private ConstraintImpl selectorCondition;
 
     public SelectorImpl(String nodeTypeName, String selectorName) {
         this.nodeTypeName = nodeTypeName;
@@ -69,13 +79,18 @@ public class SelectorImpl extends Source
 
     @Override
     public String toString() {
-        // TODO quote nodeTypeName?
-        return nodeTypeName + " as " + getSelectorName();
+        return "[" + nodeTypeName + "] as " + selectorName;
     }
 
 
     @Override
     public void prepare(MicroKernel mk) {
+        if (queryConstraint != null) {
+            queryConstraint.restrictPushDown(this);
+        }
+        for (JoinConditionImpl c : allJoinConditions) {
+            c.restrictPushDown(this);
+        }
         index = query.getBestIndex(createFilter());
     }
 
@@ -86,20 +101,27 @@ public class SelectorImpl extends Source
 
     @Override
     public String getPlan() {
-        return  nodeTypeName + " as " + getSelectorName() + " /* " + index.getPlan(createFilter()) + " */";
+        StringBuilder buff = new StringBuilder();
+        buff.append(toString());
+        buff.append(" /* ").append(index.getPlan(createFilter()));
+        if (selectorCondition != null) {
+            buff.append(" where ").append(selectorCondition);
+        }
+        buff.append(" */");
+        return buff.toString();
     }
 
     private FilterImpl createFilter() {
         FilterImpl f = new FilterImpl(this);
         f.setNodeType(nodeTypeName);
         if (joinCondition != null) {
-            joinCondition.apply(f);
+            joinCondition.restrict(f);
         }
         if (!outerJoin) {
             // for outer joins, query constraints can't be applied to the
             // filter, because that would alter the result
             if (queryConstraint != null) {
-                queryConstraint.apply(f);
+                queryConstraint.restrict(f);
             }
         }
         return f;
@@ -107,9 +129,25 @@ public class SelectorImpl extends Source
 
     @Override
     public boolean next() {
+        while (true) {
+            if (!nextNode()) {
+                return false;
+            }
+            if (selectorCondition != null && !selectorCondition.evaluate()) {
+                continue;
+            }
+            if (joinCondition != null && !joinCondition.evaluate()) {
+                continue;
+            }
+            return true;
+        }
+    }
+
+    private boolean nextNode() {
         if (cursor == null) {
             return false;
         }
+        scanCount++;
         while (true) {
             boolean result = cursor.next();
             if (!result) {
@@ -191,4 +229,16 @@ public class SelectorImpl extends Source
         return null;
     }
 
+    public long getScanCount() {
+        return scanCount;
+    }
+
+    public void restrictSelector(ConstraintImpl constraint) {
+        if (selectorCondition == null) {
+            selectorCondition = constraint;
+        } else {
+            selectorCondition = new AndImpl(selectorCondition, constraint);
+        }
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SourceImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SourceImpl.java?rev=1382008&r1=1382007&r2=1382008&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SourceImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SourceImpl.java Fri Sep  7 13:14:40 2012
@@ -18,6 +18,7 @@
  */
 package org.apache.jackrabbit.oak.query.ast;
 
+import java.util.ArrayList;
 import org.apache.jackrabbit.mk.api.MicroKernel;
 import org.apache.jackrabbit.oak.query.Query;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
@@ -27,9 +28,38 @@ import org.apache.jackrabbit.oak.spi.sta
  */
 public abstract class SourceImpl extends AstElement {
 
+    /**
+     * The WHERE clause of the query.
+     */
     protected ConstraintImpl queryConstraint;
+
+    /**
+     * The join condition of this selector that can be evaluated at execution
+     * time. For the query "select * from nt:base as a inner join nt:base as b
+     * on a.x = b.x", the join condition "a.x = b.x" is only set for the
+     * selector b, as selector a can't evaluate it if it is executed first
+     * (until b is executed).
+     */
     protected JoinConditionImpl joinCondition;
+
+    /**
+     * The list of all join conditions this selector is involved. For the query
+     * "select * from nt:base as a inner join nt:base as b on a.x =
+     * b.x", the join condition "a.x = b.x" is set for both selectors a and b,
+     * so both can check if the property x is set.
+     */
+    protected ArrayList<JoinConditionImpl> allJoinConditions =
+            new ArrayList<JoinConditionImpl>();
+
+    /**
+     * Whether this selector is the right hand side of a join.
+     */
     protected boolean join;
+
+    /**
+     * Whether this selector is the right hand side of a left outer join.
+     * Right outer joins are converted to left outer join.
+     */
     protected boolean outerJoin;
 
     /**
@@ -42,12 +72,17 @@ public abstract class SourceImpl extends
     }
 
     /**
-     * Set the join condition (the ON ... condition).
+     * Add the join condition (the ON ... condition).
      *
      * @param joinCondition the join condition
+     * @param forThisSelector if set, the join condition can only be evaluated
+     *        when all previous selectors are executed.
      */
-    public void setJoinCondition(JoinConditionImpl joinCondition) {
-        this.joinCondition = joinCondition;
+    public void addJoinCondition(JoinConditionImpl joinCondition, boolean forThisSelector) {
+        if (forThisSelector) {
+            this.joinCondition = joinCondition;
+        }
+        allJoinConditions.add(joinCondition);
     }
 
     /**
@@ -74,7 +109,7 @@ public abstract class SourceImpl extends
      * @return the selector, or null
      */
     public abstract SelectorImpl getSelector(String selectorName);
-    
+
     /**
      * Get the selector with the given name, or fail if not found.
      *

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/UpperCaseImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/UpperCaseImpl.java?rev=1382008&r1=1382007&r2=1382008&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/UpperCaseImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/UpperCaseImpl.java Fri Sep  7 13:14:40 2012
@@ -67,4 +67,9 @@ public class UpperCaseImpl extends Dynam
         // TODO UPPER(x) conditions: can use IS NOT NULL?
     }
 
+    @Override
+    public boolean canRestrictSelector(SelectorImpl s) {
+        return operand.canRestrictSelector(s);
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java?rev=1382008&r1=1382007&r2=1382008&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java Fri Sep  7 13:14:40 2012
@@ -52,6 +52,7 @@ public abstract class AbstractQueryTest 
         return new ContentRepositoryImpl(mk, qip, im);
     }
 
+    @Override
     @Before
     public void before() throws Exception {
         super.before();

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/QueryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/QueryTest.java?rev=1382008&r1=1382007&r2=1382008&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/QueryTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/QueryTest.java Fri Sep  7 13:14:40 2012
@@ -15,6 +15,7 @@ package org.apache.jackrabbit.oak.query;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -78,7 +79,8 @@ public class QueryTest extends AbstractQ
         result = executeQuery("explain select * from [nt:base] where id = 1 order by id",
                 QueryEngineImpl.SQL2, null).getRows().iterator();
         assertTrue(result.hasNext());
-        assertEquals("nt:base as nt:base /* traverse \"//*\" */",
+        assertEquals("[nt:base] as nt:base " +
+                "/* traverse \"//*\" where nt:base.id = cast('1' as long) */",
                 result.next().getValue("plan").getString());
 
     }
@@ -103,23 +105,24 @@ public class QueryTest extends AbstractQ
                     w.println("xpath2sql " + line);
                     XPathToSQL2Converter c = new XPathToSQL2Converter();
                     String got;
-                    boolean failed;
                     try {
                         got = c.convert(line);
-                        failed = false;
+                        executeQuery(got, QueryEngineImpl.SQL2, null);
                     } catch (ParseException e) {
                         got = "invalid: " + e.getMessage().replace('\n', ' ');
-                        failed = true;
+                    } catch (Exception e) {
+                        // e.printStackTrace();
+                        got = "error: " + e.toString().replace('\n', ' ');
                     }
                     line = r.readLine().trim();
                     w.println(got);
                     if (!line.equals(got)) {
                         errors = true;
                     }
-                    if (!failed) {
-                        executeQuery(got, QueryEngineImpl.SQL2, null);
-                    }
-                } else if (line.startsWith("select") || line.startsWith("explain") || line.startsWith("sql1")) {
+                } else if (line.startsWith("select") ||
+                        line.startsWith("explain") ||
+                        line.startsWith("measure") ||
+                        line.startsWith("sql1")) {
                     w.println(line);
                     String language = QueryEngineImpl.SQL2;
                     if (line.startsWith("sql1")) {
@@ -169,6 +172,7 @@ public class QueryTest extends AbstractQ
                     String diff = line.substring(spaceIndex).trim();
                     mk.commit(path, diff, mk.getHeadRevision(), "");
                 }
+                w.flush();
             }
         } finally {
             w.close();
@@ -181,6 +185,7 @@ public class QueryTest extends AbstractQ
     }
 
     private List<String> executeQuery(String query, String language) {
+        long time = System.currentTimeMillis();
         List<String> lines = new ArrayList<String>();
         try {
             Result result = executeQuery(query, language, null);
@@ -195,6 +200,10 @@ public class QueryTest extends AbstractQ
         } catch (IllegalArgumentException e) {
             lines.add(e.toString());
         }
+        time = System.currentTimeMillis() - time;
+        if (time > 3000) {
+            fail("Query took too long: " + query + " took " + time + " ms");
+        }
         return lines;
     }