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/03/20 16:05:06 UTC

svn commit: r1302927 [1/4] - in /jackrabbit/oak/trunk/oak-core/src: main/java/org/apache/jackrabbit/oak/query/ main/java/org/apache/jackrabbit/oak/query/ast/ main/java/org/apache/jackrabbit/oak/query/index/ test/java/org/apache/jackrabbit/oak/ test/jav...

Author: thomasm
Date: Tue Mar 20 15:05:04 2012
New Revision: 1302927

URL: http://svn.apache.org/viewvc?rev=1302927&view=rev
Log:
OAK-28 Query implementation (moved from the sandbox)

Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/PropertyType.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngine.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Row.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/Value.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ValueFactory.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/
    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/AstElement.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElementFactory.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstVisitor.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstVisitorBase.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/BindVariableValueImpl.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/ColumnImpl.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/JoinType.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/LiteralImpl.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/Operator.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/Order.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/OrderingImpl.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/StaticOperandImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/UpperCaseImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/Cursor.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/Filter.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/NodeReader.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/TraversingCursor.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/TraversingReader.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/
    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/
    jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/
    jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/queryTest.txt
    jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/queryXpathTest.txt

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/PropertyType.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/PropertyType.java?rev=1302927&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/PropertyType.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/PropertyType.java Tue Mar 20 15:05:04 2012
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.query;
+
+public class PropertyType {
+
+    public static final int UNDEFINED = 0;
+    public static final int STRING = 1;
+    public static final int BINARY = 2;
+    public static final int LONG = 3;
+    public static final int DOUBLE = 4;
+    public static final int DATE = 5;
+    public static final int BOOLEAN = 6;
+    public static final int NAME = 7;
+    public static final int PATH = 8;
+    public static final int REFERENCE = 9;
+    public static final int WEAKREFERENCE = 10;
+    public static final int URI = 11;
+    public static final int DECIMAL = 12;
+
+}

Added: 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=1302927&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java Tue Mar 20 15:05:04 2012
@@ -0,0 +1,433 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
+ * or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.apache.jackrabbit.oak.query;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import org.apache.jackrabbit.mk.api.MicroKernel;
+import org.apache.jackrabbit.oak.query.ast.AstVisitorBase;
+import org.apache.jackrabbit.oak.query.ast.BindVariableValueImpl;
+import org.apache.jackrabbit.oak.query.ast.ChildNodeImpl;
+import org.apache.jackrabbit.oak.query.ast.ChildNodeJoinConditionImpl;
+import org.apache.jackrabbit.oak.query.ast.ColumnImpl;
+import org.apache.jackrabbit.oak.query.ast.ComparisonImpl;
+import org.apache.jackrabbit.oak.query.ast.ConstraintImpl;
+import org.apache.jackrabbit.oak.query.ast.DescendantNodeImpl;
+import org.apache.jackrabbit.oak.query.ast.DescendantNodeJoinConditionImpl;
+import org.apache.jackrabbit.oak.query.ast.EquiJoinConditionImpl;
+import org.apache.jackrabbit.oak.query.ast.FullTextSearchScoreImpl;
+import org.apache.jackrabbit.oak.query.ast.LengthImpl;
+import org.apache.jackrabbit.oak.query.ast.LiteralImpl;
+import org.apache.jackrabbit.oak.query.ast.LowerCaseImpl;
+import org.apache.jackrabbit.oak.query.ast.NodeLocalNameImpl;
+import org.apache.jackrabbit.oak.query.ast.NodeNameImpl;
+import org.apache.jackrabbit.oak.query.ast.OrderingImpl;
+import org.apache.jackrabbit.oak.query.ast.PropertyExistenceImpl;
+import org.apache.jackrabbit.oak.query.ast.PropertyValueImpl;
+import org.apache.jackrabbit.oak.query.ast.SameNodeImpl;
+import org.apache.jackrabbit.oak.query.ast.SameNodeJoinConditionImpl;
+import org.apache.jackrabbit.oak.query.ast.SelectorImpl;
+import org.apache.jackrabbit.oak.query.ast.SourceImpl;
+import org.apache.jackrabbit.oak.query.ast.UpperCaseImpl;
+
+/**
+ * Represents a parsed query. Lifecycle: use the constructor to create a new
+ * object. Call init() to initialize the bind variable map.
+ */
+public class Query {
+
+    final SourceImpl source;
+    final ConstraintImpl constraint;
+    final HashMap<String, Value> bindVariableMap = new HashMap<String, Value>();
+    final ArrayList<SelectorImpl> selectors = new ArrayList<SelectorImpl>();
+
+    private MicroKernel mk;
+    private final OrderingImpl[] orderings;
+    private final ColumnImpl[] columns;
+    private boolean explain;
+    private long limit;
+    private long offset;
+    private boolean prepared;
+    private final ValueFactory valueFactory = new ValueFactory();
+
+    Query(SourceImpl source, ConstraintImpl constraint, OrderingImpl[] orderings,
+            ColumnImpl[] columns) {
+        this.source = source;
+        this.constraint = constraint;
+        this.orderings = orderings;
+        this.columns = columns;
+    }
+
+    public void init() {
+
+        final Query query = this;
+
+        new AstVisitorBase() {
+
+            @Override
+            public boolean visit(BindVariableValueImpl node) {
+                node.setQuery(query);
+                bindVariableMap.put(node.getBindVariableName(), null);
+                return true;
+            }
+
+            @Override
+            public boolean visit(ChildNodeImpl node) {
+                node.setQuery(query);
+                node.bindSelector(source);
+                return true;
+            }
+
+            @Override
+            public boolean visit(ChildNodeJoinConditionImpl node) {
+                node.setQuery(query);
+                node.bindSelector(source);
+                return true;
+            }
+
+            @Override
+            public boolean visit(ColumnImpl node) {
+                node.setQuery(query);
+                return true;
+            }
+
+            @Override
+            public boolean visit(DescendantNodeImpl node) {
+                node.setQuery(query);
+                node.bindSelector(source);
+                return true;
+            }
+
+            @Override
+            public boolean visit(DescendantNodeJoinConditionImpl node) {
+                node.setQuery(query);
+                node.bindSelector(source);
+                return true;
+            }
+
+            @Override
+            public boolean visit(EquiJoinConditionImpl node) {
+                node.setQuery(query);
+                node.bindSelector(source);
+                return true;
+            }
+
+            @Override
+            public boolean visit(FullTextSearchScoreImpl node) {
+                node.setQuery(query);
+                return true;
+            }
+
+            @Override
+            public boolean visit(LiteralImpl node) {
+                node.setQuery(query);
+                return true;
+            }
+
+            @Override
+            public boolean visit(NodeLocalNameImpl node) {
+                node.setQuery(query);
+                node.bindSelector(source);
+                return true;
+            }
+
+            @Override
+            public boolean visit(NodeNameImpl node) {
+                node.setQuery(query);
+                node.bindSelector(source);
+                return true;
+            }
+
+            @Override
+            public boolean visit(PropertyExistenceImpl node) {
+                node.setQuery(query);
+                node.bindSelector(source);
+                return true;
+            }
+
+            @Override
+            public boolean visit(PropertyValueImpl node) {
+                node.setQuery(query);
+                node.bindSelector(source);
+                return true;
+            }
+
+            @Override
+            public boolean visit(SameNodeImpl node) {
+                node.setQuery(query);
+                node.bindSelector(source);
+                return true;
+            }
+
+            @Override
+            public boolean visit(SameNodeJoinConditionImpl node) {
+                node.setQuery(query);
+                node.bindSelector(source);
+                return true;
+            }
+
+            @Override
+            public boolean visit(SelectorImpl node) {
+                node.setQuery(query);
+                return true;
+            }
+
+            @Override
+            public boolean visit(LengthImpl node) {
+                node.setQuery(query);
+                return super.visit(node);
+            }
+
+            @Override
+            public boolean visit(UpperCaseImpl node) {
+                node.setQuery(query);
+                return super.visit(node);
+            }
+
+            @Override
+            public boolean visit(LowerCaseImpl node) {
+                node.setQuery(query);
+                return super.visit(node);
+            }
+
+            @Override
+            public boolean visit(ComparisonImpl node) {
+                node.setQuery(query);
+                return super.visit(node);
+            }
+
+        }.visit(this);
+        source.init(this);
+        source.setQueryConstraint(constraint);
+        for (ColumnImpl column : columns) {
+            column.bindSelector(source);
+        }
+    }
+
+    public ColumnImpl[] getColumns() {
+        return columns;
+    }
+
+    public ConstraintImpl getConstraint() {
+        return constraint;
+    }
+
+    public OrderingImpl[] getOrderings() {
+        return orderings;
+    }
+
+    public SourceImpl getSource() {
+        return source;
+    }
+
+    void bindValue(String varName, Value value) {
+        bindVariableMap.put(varName, value);
+    }
+
+    public void setMicroKernel(MicroKernel mk) {
+        this.mk = mk;
+    }
+
+    public void setLimit(long limit) {
+        this.limit = limit;
+    }
+
+    public void setOffset(long offset) {
+        this.offset = offset;
+    }
+
+    public ValueFactory getValueFactory() {
+        return valueFactory;
+    }
+
+    public void setExplain(boolean explain) {
+        this.explain = explain;
+    }
+
+    public Iterator<Row> executeQuery(String revisionId) {
+        prepare();
+        if (explain) {
+            String plan = source.getPlan();
+            Row r = new Row(this, null, new Value[] { valueFactory.createValue(plan) }, null);
+            return Arrays.asList(r).iterator();
+        }
+        RowIterator it = new RowIterator(revisionId);
+        if (orderings == null) {
+            return it;
+        }
+        // TODO "order by" is not necessary if the used index returns rows in the same order
+        ArrayList<Row> list = new ArrayList<Row>();
+        while (it.hasNext()) {
+            Row r = it.next();
+            list.add(r);
+        }
+        Collections.sort(list);
+        return list.iterator();
+    }
+
+    public int compareRows(Value[] orderValues, Value[] orderValues2) {
+        int comp = 0;
+        for (int i = 0, size = orderings.length; i < size; i++) {
+            Value a = orderValues[i];
+            Value b = orderValues2[i];
+            // TODO order by: currently use string compare
+            String as = a == null ? null : a.getString();
+            String bs = b == null ? null : b.getString();
+            if (as == null || bs == null) {
+                if (as == bs) {
+                    comp = 0;
+                } else if (as == null) {
+                    // TODO order by: nulls first, last, low or high?
+                    comp = 1;
+                }
+            } else {
+                comp = as.compareTo(bs);
+            }
+            if (comp != 0) {
+                if (orderings[i].isDescending()) {
+                    comp = -comp;
+                }
+                break;
+            }
+        }
+        return comp;
+    }
+
+    private void prepare() {
+        if (prepared) {
+            return;
+        }
+        source.prepare(mk);
+    }
+
+    class RowIterator implements Iterator<Row> {
+
+        private final String revisionId;
+        private Row current;
+        private boolean started, end;
+
+        RowIterator(String revisionId) {
+            this.revisionId = revisionId;
+        }
+
+        private void fetchNext() {
+            if (end) {
+                return;
+            }
+            if (!started) {
+                source.execute(revisionId);
+                started = true;
+            }
+            while (true) {
+                if (source.next()) {
+                    if (constraint == null || constraint.evaluate()) {
+                        current = currentRow();
+                        break;
+                    }
+                } else {
+                    current = null;
+                    end = true;
+                    break;
+                }
+            }
+        }
+
+        @Override
+        public boolean hasNext() {
+            if (end) {
+                return false;
+            }
+            if (current == null) {
+                fetchNext();
+            }
+            return !end;
+        }
+
+        @Override
+        public Row next() {
+            if (end) {
+                return null;
+            }
+            if (current == null) {
+                fetchNext();
+            }
+            Row r = current;
+            current = null;
+            return r;
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+
+    }
+
+    Row currentRow() {
+        int selectorCount = selectors.size();
+        String[] paths = new String[selectorCount];
+        for (int i = 0; i < selectorCount; i++) {
+            SelectorImpl s = selectors.get(i);
+            paths[i] = s.currentPath();
+        }
+        int columnCount = columns.length;
+        Value[] values = new Value[columnCount];
+        for (int i = 0; i < columnCount; i++) {
+            ColumnImpl c = columns[i];
+            values[i] = c.currentValue();
+        }
+        Value[] orderValues;
+        if (orderings == null) {
+            orderValues = null;
+        } else {
+            int size = orderings.length;
+            orderValues = new Value[size];
+            for (int i = 0; i < size; i++) {
+                orderValues[i] = orderings[i].getOperand().currentValue();
+            }
+        }
+        return new Row(this, paths, values, orderValues);
+    }
+
+    public int getSelectorIndex(String selectorName) {
+        for (int i = 0, size = selectors.size(); i < size; i++) {
+            if (selectors.get(i).getSelectorName().equals(selectorName)) {
+                return i;
+            }
+        }
+        throw new IllegalArgumentException("Unknown selector: " + selectorName);
+    }
+
+    public int getColumnIndex(String columnName) {
+        for (int i = 0, size = columns.length; i < size; i++) {
+            ColumnImpl c = columns[i];
+            if (c.getColumnName().equals(columnName)) {
+                return i;
+            }
+        }
+        throw new IllegalArgumentException("Column not found: " + columnName);
+    }
+
+    public long getLimit() {
+        return limit;
+    }
+
+    public long getOffset() {
+        return offset;
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngine.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngine.java?rev=1302927&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngine.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngine.java Tue Mar 20 15:05:04 2012
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.query;
+
+import java.text.ParseException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import org.apache.jackrabbit.mk.api.MicroKernel;
+
+public class QueryEngine {
+
+    public static final String XPATH = "xpath";
+    public static final String SQL2 = "sql2";
+
+    private final MicroKernel mk;
+    private final ValueFactory vf = new ValueFactory();
+    private final SQL2Parser parserSQL2;
+
+    private QueryEngine(MicroKernel mk) {
+        this.mk = mk;
+        parserSQL2 = new SQL2Parser(vf);
+    }
+
+    public static QueryEngine getInstance(MicroKernel mk) {
+        return new QueryEngine(mk);
+    }
+
+    public Iterator<Row> executeQuery(String language, String query, Map<String, String> bindings) throws ParseException {
+        Query q;
+        if (SQL2.equals(language)) {
+            q = parserSQL2.parse(query);
+        } else if (XPATH.equals(language)) {
+            XPathToSQL2Converter converter = new XPathToSQL2Converter();
+            String sql2 = converter.convert(query);
+            q = parserSQL2.parse(sql2);
+        } else {
+            throw new ParseException("Unsupported language: " + language, 0);
+        }
+        q.setMicroKernel(mk);
+        if (bindings != null) {
+            for (Entry<String, String> e : bindings.entrySet()) {
+                q.bindValue(e.getKey(), vf.createValue(e.getValue()));
+            }
+        }
+        return q.executeQuery(mk.getHeadRevision());
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Row.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Row.java?rev=1302927&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Row.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Row.java Tue Mar 20 15:05:04 2012
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.query;
+
+/**
+ * A query result row that keeps all data in memory.
+ */
+public class Row implements Comparable<Row> {
+
+    private final Query qom;
+    private final String[] paths;
+    private final Value[] values;
+    private final Value[] orderValues;
+
+    Row(Query qom, String[] paths, Value[] values, Value[] orderValues) {
+        this.qom = qom;
+        this.paths = paths;
+        this.values = values;
+        this.orderValues = orderValues;
+    }
+
+    public String getPath() {
+        if (paths.length > 0) {
+            throw new IllegalArgumentException("More than one selector");
+        }
+        return paths[0];
+    }
+
+    public String getPath(String selectorName) {
+        return paths[qom.getSelectorIndex(selectorName)];
+    }
+
+    public Value getValue(String columnName) {
+        return values[qom.getColumnIndex(columnName)];
+    }
+
+    public Value[] getValues() {
+        Value[] v2 = new Value[values.length];
+        System.arraycopy(values, 0, v2, 0, v2.length);
+        return v2;
+    }
+
+    @Override
+    public int compareTo(Row o) {
+        return qom.compareRows(orderValues, o.orderValues);
+    }
+
+}

Added: 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=1302927&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SQL2Parser.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SQL2Parser.java Tue Mar 20 15:05:04 2012
@@ -0,0 +1,1003 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.query;
+
+import org.apache.jackrabbit.oak.query.ast.AstElementFactory;
+import org.apache.jackrabbit.oak.query.ast.Operator;
+import org.apache.jackrabbit.oak.query.ast.BindVariableValueImpl;
+import org.apache.jackrabbit.oak.query.ast.ColumnImpl;
+import org.apache.jackrabbit.oak.query.ast.ConstraintImpl;
+import org.apache.jackrabbit.oak.query.ast.DynamicOperandImpl;
+import org.apache.jackrabbit.oak.query.ast.JoinConditionImpl;
+import org.apache.jackrabbit.oak.query.ast.JoinType;
+import org.apache.jackrabbit.oak.query.ast.LiteralImpl;
+import org.apache.jackrabbit.oak.query.ast.OrderingImpl;
+import org.apache.jackrabbit.oak.query.ast.PropertyExistenceImpl;
+import org.apache.jackrabbit.oak.query.ast.PropertyValueImpl;
+import org.apache.jackrabbit.oak.query.ast.SelectorImpl;
+import org.apache.jackrabbit.oak.query.ast.SourceImpl;
+import org.apache.jackrabbit.oak.query.ast.StaticOperandImpl;
+
+import java.math.BigDecimal;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * The SQL2 parser can convert a JCR-SQL2 query to a query.
+ */
+public class SQL2Parser {
+
+    // Character types, used during the tokenizer phase
+    private static final int CHAR_END = -1, CHAR_VALUE = 2, CHAR_QUOTED = 3;
+    private static final int CHAR_NAME = 4, CHAR_SPECIAL_1 = 5, CHAR_SPECIAL_2 = 6;
+    private static final int CHAR_STRING = 7, CHAR_DECIMAL = 8;
+
+    // Token types
+    private static final int KEYWORD = 1, IDENTIFIER = 2, PARAMETER = 3, END = 4, VALUE = 5;
+    private static final int MINUS = 12, PLUS = 13, OPEN = 14, CLOSE = 15;
+
+    // The query as an array of characters and character types
+    private String statement;
+    private char[] statementChars;
+    private int[] characterTypes;
+
+    // The current state of the parser
+    private int parseIndex;
+    private int currentTokenType;
+    private String currentToken;
+    private boolean currentTokenQuoted;
+    private Value currentValue;
+    private ArrayList<String> expected;
+
+    // The bind variables
+    private HashMap<String, BindVariableValueImpl> bindVariables;
+
+    // The list of selectors of this query
+    private ArrayList<SelectorImpl> selectors;
+
+    // SQL injection protection: if disabled, literals are not allowed
+    private boolean allowTextLiterals = true;
+    private boolean allowNumberLiterals = true;
+
+    private final AstElementFactory factory = new AstElementFactory();
+    private final ValueFactory valueFactory;
+
+    /**
+     * Create a new parser. A parser can be re-used, but it is not thread safe.
+     *
+     * @param valueFactory the value factory
+     */
+    public SQL2Parser(ValueFactory valueFactory) {
+        this.valueFactory = valueFactory;
+    }
+
+    /**
+     * Parse the statement and return the query.
+     *
+     * @param query the query string
+     * @return the query
+     * @throws ParseException if parsing fails
+     */
+    public Query parse(String query) throws ParseException {
+        initialize(query);
+        selectors = new ArrayList<SelectorImpl>();
+        expected = new ArrayList<String>();
+        bindVariables = new HashMap<String, BindVariableValueImpl>();
+        read();
+        boolean explain = readIf("EXPLAIN");
+        read("SELECT");
+        ArrayList<ColumnOrWildcard> list = parseColumns();
+        read("FROM");
+        SourceImpl source = parseSource();
+        ColumnImpl[] columnArray = resolveColumns(list);
+        ConstraintImpl constraint = null;
+        if (readIf("WHERE")) {
+            constraint = parseConstraint();
+        }
+        OrderingImpl[] orderings = null;
+        if (readIf("ORDER")) {
+            read("BY");
+            orderings = parseOrder();
+        }
+        if (!currentToken.isEmpty()) {
+            throw getSyntaxError("<end>");
+        }
+        Query q = new Query(source, constraint, orderings, columnArray);
+        q.setExplain(explain);
+        q.init();
+        return q;
+    }
+
+    private SelectorImpl parseSelector() throws ParseException {
+        String nodeTypeName = readName();
+        if (readIf("AS")) {
+            String selectorName = readName();
+            return factory.selector(nodeTypeName, selectorName);
+        } else {
+            return factory.selector(nodeTypeName, nodeTypeName);
+        }
+    }
+
+    private String readName() throws ParseException {
+        if (readIf("[")) {
+            if (currentTokenType == VALUE) {
+                Value value = readString();
+                read("]");
+                return value.getString();
+            } else {
+                int level = 1;
+                StringBuilder buff = new StringBuilder();
+                while (true) {
+                    if (isToken("]")) {
+                        if (--level <= 0) {
+                            read();
+                            break;
+                        }
+                    } else if (isToken("[")) {
+                        level++;
+                    }
+                    buff.append(readAny());
+                }
+                return buff.toString();
+            }
+        } else {
+            return readAny();
+        }
+    }
+
+    private SourceImpl parseSource() throws ParseException {
+        SelectorImpl selector = parseSelector();
+        selectors.add(selector);
+        SourceImpl source = selector;
+        while (true) {
+            JoinType joinType;
+            if (readIf("RIGHT")) {
+                read("OUTER");
+                joinType = JoinType.RIGHT_OUTER;
+            } else if (readIf("LEFT")) {
+                read("OUTER");
+                joinType = JoinType.LEFT_OUTER;
+            } else if (readIf("INNER")) {
+                joinType = JoinType.INNER;
+            } else {
+                break;
+            }
+            read("JOIN");
+            selector = parseSelector();
+            selectors.add(selector);
+            read("ON");
+            JoinConditionImpl on = parseJoinCondition();
+            source = factory.join(source, selector, joinType, on);
+        }
+        return source;
+    }
+
+    private JoinConditionImpl parseJoinCondition() throws ParseException {
+        boolean identifier = currentTokenType == IDENTIFIER;
+        String name = readName();
+        JoinConditionImpl c;
+        if (identifier && readIf("(")) {
+            if ("ISSAMENODE".equalsIgnoreCase(name)) {
+                String selector1 = readName();
+                read(",");
+                String selector2 = readName();
+                if (readIf(",")) {
+                    c = factory.sameNodeJoinCondition(selector1, selector2, readPath());
+                } else {
+                    // TODO verify "." is correct
+                    c = factory.sameNodeJoinCondition(selector1, selector2, ".");
+                }
+            } else if ("ISCHILDNODE".equalsIgnoreCase(name)) {
+                String childSelector = readName();
+                read(",");
+                c = factory.childNodeJoinCondition(childSelector, readName());
+            } else if ("ISDESCENDANTNODE".equalsIgnoreCase(name)) {
+                String descendantSelector = readName();
+                read(",");
+                c = factory.descendantNodeJoinCondition(descendantSelector, readName());
+            } else {
+                throw getSyntaxError("ISSAMENODE, ISCHILDNODE, or ISDESCENDANTNODE");
+            }
+            read(")");
+            return c;
+        } else {
+            String selector1 = name;
+            read(".");
+            String property1 = readName();
+            read("=");
+            String selector2 = readName();
+            read(".");
+            return factory.equiJoinCondition(selector1, property1, selector2, readName());
+        }
+    }
+
+    private ConstraintImpl parseConstraint() throws ParseException {
+        ConstraintImpl a = parseAnd();
+        while (readIf("OR")) {
+            a = factory.or(a, parseAnd());
+        }
+        return a;
+    }
+
+    private ConstraintImpl parseAnd() throws ParseException {
+        ConstraintImpl a = parseCondition();
+        while (readIf("AND")) {
+            a = factory.and(a, parseCondition());
+        }
+        return a;
+    }
+
+    private ConstraintImpl parseCondition() throws ParseException {
+        ConstraintImpl a;
+        if (readIf("NOT")) {
+            a = factory.not(parseConstraint());
+        } else if (readIf("(")) {
+            a = parseConstraint();
+            read(")");
+        } else if (currentTokenType == IDENTIFIER) {
+            String identifier = readName();
+            if (readIf("(")) {
+                a = parseConditionFunctionIf(identifier);
+                if (a == null) {
+                    DynamicOperandImpl op = parseExpressionFunction(identifier);
+                    a = parseCondition(op);
+                }
+            } else if (readIf(".")) {
+                a = parseCondition(factory.propertyValue(identifier, readName()));
+            } else {
+                a = parseCondition(factory.propertyValue(getOnlySelectorName(), identifier));
+            }
+        } else if ("[".equals(currentToken)) {
+            String name = readName();
+            if (readIf(".")) {
+                a = parseCondition(factory.propertyValue(name, readName()));
+            } else {
+                a = parseCondition(factory.propertyValue(getOnlySelectorName(), name));
+            }
+        } else {
+            throw getSyntaxError();
+        }
+        return a;
+    }
+
+    private ConstraintImpl parseCondition(DynamicOperandImpl left) throws ParseException {
+        ConstraintImpl c;
+        if (readIf("=")) {
+            c = factory.comparison(left, Operator.EQ, parseStaticOperand());
+        } else if (readIf("<>")) {
+            c = factory.comparison(left, Operator.NE, parseStaticOperand());
+        } else if (readIf("<")) {
+            c = factory.comparison(left, Operator.LT, parseStaticOperand());
+        } else if (readIf(">")) {
+            c = factory.comparison(left, Operator.GT, parseStaticOperand());
+        } else if (readIf("<=")) {
+            c = factory.comparison(left, Operator.LE, parseStaticOperand());
+        } else if (readIf(">=")) {
+            c = factory.comparison(left, Operator.GE, parseStaticOperand());
+        } else if (readIf("LIKE")) {
+            c = factory.comparison(left, Operator.LIKE, parseStaticOperand());
+        } else if (readIf("IS")) {
+            boolean not = readIf("NOT");
+            read("NULL");
+            if (!(left instanceof PropertyValueImpl)) {
+                throw getSyntaxError("propertyName (NOT NULL is only supported for properties)");
+            }
+            PropertyValueImpl p = (PropertyValueImpl) left;
+            c = getPropertyExistence(p);
+            if (!not) {
+                c = factory.not(c);
+            }
+        } else if (readIf("NOT")) {
+            if (readIf("IS")) {
+                read("NULL");
+                if (!(left instanceof PropertyValueImpl)) {
+                    throw new ParseException(
+                            "Only property values can be tested for NOT IS NULL; got: "
+                            + left.getClass().getName(), parseIndex);
+                }
+                PropertyValueImpl pv = (PropertyValueImpl) left;
+                c = getPropertyExistence(pv);
+            } else {
+                read("LIKE");
+                c = factory.comparison(left, Operator.LIKE, parseStaticOperand());
+                c = factory.not(c);
+            }
+        } else {
+            throw getSyntaxError();
+        }
+        return c;
+    }
+
+    private PropertyExistenceImpl getPropertyExistence(PropertyValueImpl p) throws ParseException {
+        return factory.propertyExistence(p.getSelectorName(), p.getPropertyName());
+    }
+
+    private ConstraintImpl parseConditionFunctionIf(String functionName) throws ParseException {
+        ConstraintImpl c;
+        if ("CONTAINS".equalsIgnoreCase(functionName)) {
+            String name = readName();
+            if (readIf(".")) {
+                if (readIf("*")) {
+                    read(",");
+                    c = factory.fullTextSearch(
+                            name, null, parseStaticOperand());
+                } else {
+                    String selector = name;
+                    name = readName();
+                    read(",");
+                    c = factory.fullTextSearch(
+                            selector, name, parseStaticOperand());
+                }
+            } else {
+                read(",");
+                c = factory.fullTextSearch(
+                        getOnlySelectorName(), name,
+                        parseStaticOperand());
+            }
+        } else if ("ISSAMENODE".equalsIgnoreCase(functionName)) {
+            String name = readName();
+            if (readIf(",")) {
+                c = factory.sameNode(name, readPath());
+            } else {
+                c = factory.sameNode(getOnlySelectorName(), name);
+            }
+        } else if ("ISCHILDNODE".equalsIgnoreCase(functionName)) {
+            String name = readName();
+            if (readIf(",")) {
+                c = factory.childNode(name, readPath());
+            } else {
+                c = factory.childNode(getOnlySelectorName(), name);
+            }
+        } else if ("ISDESCENDANTNODE".equalsIgnoreCase(functionName)) {
+            String name = readName();
+            if (readIf(",")) {
+                c = factory.descendantNode(name, readPath());
+            } else {
+                c = factory.descendantNode(getOnlySelectorName(), name);
+            }
+        } else {
+            return null;
+        }
+        read(")");
+        return c;
+    }
+
+    private String readPath() throws ParseException {
+        return readName();
+    }
+
+    private DynamicOperandImpl parseDynamicOperand() throws ParseException {
+        boolean identifier = currentTokenType == IDENTIFIER;
+        String name = readName();
+        if (identifier && readIf("(")) {
+            return parseExpressionFunction(name);
+        } else {
+            return parsePropertyValue(name);
+        }
+    }
+
+    private DynamicOperandImpl parseExpressionFunction(String functionName) throws ParseException {
+        DynamicOperandImpl op;
+        if ("LENGTH".equalsIgnoreCase(functionName)) {
+            op = factory.length(parsePropertyValue(readName()));
+        } else if ("NAME".equalsIgnoreCase(functionName)) {
+            if (isToken(")")) {
+                op = factory.nodeName(getOnlySelectorName());
+            } else {
+                op = factory.nodeName(readName());
+            }
+        } else if ("LOCALNAME".equalsIgnoreCase(functionName)) {
+            if (isToken(")")) {
+                op = factory.nodeLocalName(getOnlySelectorName());
+            } else {
+                op = factory.nodeLocalName(readName());
+            }
+        } else if ("SCORE".equalsIgnoreCase(functionName)) {
+            if (isToken(")")) {
+                op = factory.fullTextSearchScore(getOnlySelectorName());
+            } else {
+                op = factory.fullTextSearchScore(readName());
+            }
+        } else if ("LOWER".equalsIgnoreCase(functionName)) {
+            op = factory.lowerCase(parseDynamicOperand());
+        } else if ("UPPER".equalsIgnoreCase(functionName)) {
+            op = factory.upperCase(parseDynamicOperand());
+        } else {
+            throw getSyntaxError("LENGTH, NAME, LOCALNAME, SCORE, LOWER, UPPER, or CAST");
+        }
+        read(")");
+        return op;
+    }
+
+    private PropertyValueImpl parsePropertyValue(String name) throws ParseException {
+        if (readIf(".")) {
+            return factory.propertyValue(name, readName());
+        } else {
+            return factory.propertyValue(getOnlySelectorName(), name);
+        }
+    }
+
+    private StaticOperandImpl parseStaticOperand() throws ParseException {
+        if (currentTokenType == PLUS) {
+            read();
+        } else if (currentTokenType == MINUS) {
+            read();
+            if (currentTokenType != VALUE) {
+                throw getSyntaxError("number");
+            }
+            int valueType = currentValue.getType();
+            switch (valueType) {
+            case PropertyType.LONG:
+                currentValue = valueFactory.createValue(-currentValue.getLong());
+                break;
+            case PropertyType.DOUBLE:
+                currentValue = valueFactory.createValue(-currentValue.getDouble());
+                break;
+            case PropertyType.BOOLEAN:
+                currentValue = valueFactory.createValue(!currentValue.getBoolean());
+                break;
+            case PropertyType.DECIMAL:
+                currentValue = valueFactory.createValue(currentValue.getDecimal().negate());
+                break;
+            default:
+                throw getSyntaxError("Illegal operation: -" + currentValue);
+            }
+        }
+        if (currentTokenType == VALUE) {
+            LiteralImpl literal = getUncastLiteral(currentValue);
+            read();
+            return literal;
+        } else if (currentTokenType == PARAMETER) {
+            read();
+            String name = readName();
+            if (readIf(":")) {
+                name = name + ':' + readName();
+            }
+            BindVariableValueImpl var = bindVariables.get(name);
+            if (var == null) {
+                var = factory.bindVariable(name);
+                bindVariables.put(name, var);
+            }
+            return var;
+        } else if (readIf("TRUE")) {
+            LiteralImpl literal = getUncastLiteral(valueFactory.createValue(true));
+            return literal;
+        } else if (readIf("FALSE")) {
+            LiteralImpl literal = getUncastLiteral(valueFactory.createValue(false));
+            return literal;
+        } else if (readIf("CAST")) {
+            read("(");
+            StaticOperandImpl op = parseStaticOperand();
+            if (!(op instanceof LiteralImpl)) {
+                throw getSyntaxError("literal");
+            }
+            LiteralImpl literal = (LiteralImpl) op;
+            Value value = literal.getLiteralValue();
+            read("AS");
+            value = parseCastAs(value);
+            read(")");
+            // CastLiteral
+            literal = factory.literal(value);
+            return literal;
+        } else {
+            throw getSyntaxError("static operand");
+        }
+    }
+
+    /**
+     * Create a literal from a parsed value.
+     *
+     * @param value the original value
+     * @return the literal
+     */
+    private LiteralImpl getUncastLiteral(Value value) throws ParseException {
+        return factory.literal(value);
+    }
+
+    private Value parseCastAs(Value value) throws ParseException {
+        if (readIf("STRING")) {
+            return valueFactory.createValue(value.getString());
+        } else if (readIf("BINARY")) {
+            return valueFactory.createValue(value.getBinary());
+        } else if (readIf("DATE")) {
+            return valueFactory.createValue(value.getDate());
+        } else if (readIf("LONG")) {
+            return valueFactory.createValue(value.getLong());
+        } else if (readIf("DOUBLE")) {
+            return valueFactory.createValue(value.getDouble());
+        } else if (readIf("DECIMAL")) {
+            return valueFactory.createValue(value.getDecimal());
+        } else if (readIf("BOOLEAN")) {
+            return valueFactory.createValue(value.getBoolean());
+        } else if (readIf("NAME")) {
+            return valueFactory.createValue(value.getString(), PropertyType.NAME);
+        } else if (readIf("PATH")) {
+            return valueFactory.createValue(value.getString(), PropertyType.PATH);
+        } else if (readIf("REFERENCE")) {
+            return valueFactory.createValue(value.getString(), PropertyType.REFERENCE);
+        } else if (readIf("WEAKREFERENCE")) {
+            return valueFactory.createValue(value.getString(), PropertyType.WEAKREFERENCE);
+        } else if (readIf("URI")) {
+            return valueFactory.createValue(value.getString(), PropertyType.URI);
+        } else {
+            throw getSyntaxError("data type (STRING|BINARY|...)");
+        }
+    }
+
+    private OrderingImpl[] parseOrder() throws ParseException {
+        ArrayList<OrderingImpl> orderList = new ArrayList<OrderingImpl>();
+        do {
+            OrderingImpl ordering;
+            DynamicOperandImpl op = parseDynamicOperand();
+            if (readIf("DESC")) {
+                ordering = factory.descending(op);
+            } else {
+                readIf("ASC");
+                ordering = factory.ascending(op);
+            }
+            orderList.add(ordering);
+        } while (readIf(","));
+        OrderingImpl[] orderings = new OrderingImpl[orderList.size()];
+        orderList.toArray(orderings);
+        return orderings;
+    }
+
+    private ArrayList<ColumnOrWildcard> parseColumns() throws ParseException {
+        ArrayList<ColumnOrWildcard> list = new ArrayList<ColumnOrWildcard>();
+        if (readIf("*")) {
+            list.add(new ColumnOrWildcard());
+        } else {
+            do {
+                ColumnOrWildcard column = new ColumnOrWildcard();
+                column.propertyName = readName();
+                if (readIf(".")) {
+                    column.selectorName = column.propertyName;
+                    if (readIf("*")) {
+                        column.propertyName = null;
+                    } else {
+                        column.propertyName = readName();
+                        if (readIf("AS")) {
+                            column.columnName = readName();
+                        }
+                    }
+                } else {
+                    if (readIf("AS")) {
+                        column.columnName = readName();
+                    }
+                }
+                list.add(column);
+            } while (readIf(","));
+        }
+        return list;
+    }
+
+    private ColumnImpl[] resolveColumns(ArrayList<ColumnOrWildcard> list) throws ParseException {
+        ArrayList<ColumnImpl> columns = new ArrayList<ColumnImpl>();
+        for (ColumnOrWildcard c : list) {
+            if (c.propertyName == null) {
+                for (SelectorImpl selector : selectors) {
+                    if (c.selectorName == null
+                            || c.selectorName
+                                    .equals(selector.getSelectorName())) {
+                        ColumnImpl column = factory.column(selector
+                                .getSelectorName(), null, null);
+                        columns.add(column);
+                    }
+                }
+            } else {
+                ColumnImpl column;
+                if (c.selectorName != null) {
+                    column = factory.column(c.selectorName, c.propertyName, c.columnName);
+                } else if (c.columnName != null) {
+                    column = factory.column(getOnlySelectorName(), c.propertyName, c.columnName);
+                } else {
+                    column = factory.column(getOnlySelectorName(), c.propertyName, c.propertyName);
+                }
+                columns.add(column);
+            }
+        }
+        ColumnImpl[] array = new ColumnImpl[columns.size()];
+        columns.toArray(array);
+        return array;
+    }
+
+    private boolean readIf(String token) throws ParseException {
+        if (isToken(token)) {
+            read();
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isToken(String token) {
+        boolean result = token.equalsIgnoreCase(currentToken) && !currentTokenQuoted;
+        if (result) {
+            return true;
+        }
+        addExpected(token);
+        return false;
+    }
+
+    private void read(String expected) throws ParseException {
+        if (!expected.equalsIgnoreCase(currentToken) || currentTokenQuoted) {
+            throw getSyntaxError(expected);
+        }
+        read();
+    }
+
+    private String readAny() throws ParseException {
+        if (currentTokenType == END) {
+            throw getSyntaxError("a token");
+        }
+        String s;
+        if (currentTokenType == VALUE) {
+            s = currentValue.getString();
+        } else {
+            s = currentToken;
+        }
+        read();
+        return s;
+    }
+
+    private Value readString() throws ParseException {
+        if (currentTokenType != VALUE) {
+            throw getSyntaxError("string value");
+        }
+        Value value = currentValue;
+        read();
+        return value;
+    }
+
+    private void addExpected(String token) {
+        if (expected != null) {
+            expected.add(token);
+        }
+    }
+
+    private void initialize(String query) throws ParseException {
+        if (query == null) {
+            query = "";
+        }
+        statement = query;
+        int len = query.length() + 1;
+        char[] command = new char[len];
+        int[] types = new int[len];
+        len--;
+        query.getChars(0, len, command, 0);
+        command[len] = ' ';
+        int startLoop = 0;
+        for (int i = 0; i < len; i++) {
+            char c = command[i];
+            int type = 0;
+            switch (c) {
+            case '/':
+            case '-':
+            case '(':
+            case ')':
+            case '{':
+            case '}':
+            case '*':
+            case ',':
+            case ';':
+            case '+':
+            case '%':
+            case '?':
+            case '$':
+            case '[':
+            case ']':
+                type = CHAR_SPECIAL_1;
+                break;
+            case '!':
+            case '<':
+            case '>':
+            case '|':
+            case '=':
+            case ':':
+                type = CHAR_SPECIAL_2;
+                break;
+            case '.':
+                type = CHAR_DECIMAL;
+                break;
+            case '\'':
+                type = CHAR_STRING;
+                types[i] = CHAR_STRING;
+                startLoop = i;
+                while (command[++i] != '\'') {
+                    checkRunOver(i, len, startLoop);
+                }
+                break;
+            case '\"':
+                type = CHAR_QUOTED;
+                types[i] = CHAR_QUOTED;
+                startLoop = i;
+                while (command[++i] != '\"') {
+                    checkRunOver(i, len, startLoop);
+                }
+                break;
+            case '_':
+                type = CHAR_NAME;
+                break;
+            default:
+                if (c >= 'a' && c <= 'z') {
+                    type = CHAR_NAME;
+                } else if (c >= 'A' && c <= 'Z') {
+                    type = CHAR_NAME;
+                } else if (c >= '0' && c <= '9') {
+                    type = CHAR_VALUE;
+                } else {
+                    if (Character.isJavaIdentifierPart(c)) {
+                        type = CHAR_NAME;
+                    }
+                }
+            }
+            types[i] = (byte) type;
+        }
+        statementChars = command;
+        types[len] = CHAR_END;
+        characterTypes = types;
+        parseIndex = 0;
+    }
+
+    private void checkRunOver(int i, int len, int startLoop) throws ParseException {
+        if (i >= len) {
+            parseIndex = startLoop;
+            throw getSyntaxError();
+        }
+    }
+
+    private void read() throws ParseException {
+        currentTokenQuoted = false;
+        if (expected != null) {
+            expected.clear();
+        }
+        int[] types = characterTypes;
+        int i = parseIndex;
+        int type = types[i];
+        while (type == 0) {
+            type = types[++i];
+        }
+        int start = i;
+        char[] chars = statementChars;
+        char c = chars[i++];
+        currentToken = "";
+        switch (type) {
+        case CHAR_NAME:
+            while (true) {
+                type = types[i];
+                if (type != CHAR_NAME && type != CHAR_VALUE) {
+                    c = chars[i];
+                    break;
+                }
+                i++;
+            }
+            currentToken = statement.substring(start, i);
+            if (currentToken.isEmpty()) {
+                throw getSyntaxError();
+            }
+            currentTokenType = IDENTIFIER;
+            parseIndex = i;
+            return;
+        case CHAR_SPECIAL_2:
+            if (types[i] == CHAR_SPECIAL_2) {
+                i++;
+            }
+            // fall through
+        case CHAR_SPECIAL_1:
+            currentToken = statement.substring(start, i);
+            switch (c) {
+            case '$':
+                currentTokenType = PARAMETER;
+                break;
+            case '+':
+                currentTokenType = PLUS;
+                break;
+            case '-':
+                currentTokenType = MINUS;
+                break;
+            case '(':
+                currentTokenType = OPEN;
+                break;
+            case ')':
+                currentTokenType = CLOSE;
+                break;
+            default:
+                currentTokenType = KEYWORD;
+            }
+            parseIndex = i;
+            return;
+        case CHAR_VALUE:
+            long number = c - '0';
+            while (true) {
+                c = chars[i];
+                if (c < '0' || c > '9') {
+                    if (c == '.') {
+                        readDecimal(start, i);
+                        break;
+                    }
+                    if (c == 'E' || c == 'e') {
+                        readDecimal(start, i);
+                        break;
+                    }
+                    checkLiterals(false);
+                    currentValue = valueFactory.createValue(number);
+                    currentTokenType = VALUE;
+                    currentToken = "0";
+                    parseIndex = i;
+                    break;
+                }
+                number = number * 10 + (c - '0');
+                if (number > Integer.MAX_VALUE) {
+                    readDecimal(start, i);
+                    break;
+                }
+                i++;
+            }
+            return;
+        case CHAR_DECIMAL:
+            if (types[i] != CHAR_VALUE) {
+                currentTokenType = KEYWORD;
+                currentToken = ".";
+                parseIndex = i;
+                return;
+            }
+            readDecimal(i - 1, i);
+            return;
+        case CHAR_STRING:
+            readString(i, '\'');
+            return;
+        case CHAR_QUOTED:
+            readString(i, '\"');
+            return;
+        case CHAR_END:
+            currentToken = "";
+            currentTokenType = END;
+            parseIndex = i;
+            return;
+        default:
+            throw getSyntaxError();
+        }
+    }
+
+    private void readString(int i, char end) throws ParseException {
+        char[] chars = statementChars;
+        String result = null;
+        while (true) {
+            for (int begin = i;; i++) {
+                if (chars[i] == end) {
+                    if (result == null) {
+                        result = statement.substring(begin, i);
+                    } else {
+                        result += statement.substring(begin - 1, i);
+                    }
+                    break;
+                }
+            }
+            if (chars[++i] != end) {
+                break;
+            }
+            i++;
+        }
+        currentToken = "'";
+        checkLiterals(false);
+        currentValue = valueFactory.createValue(result);
+        parseIndex = i;
+        currentTokenType = VALUE;
+    }
+
+    private void checkLiterals(boolean text) throws ParseException {
+        if (text && !allowTextLiterals || !text && !allowNumberLiterals) {
+            throw getSyntaxError("bind variable (literals of this type not allowed)");
+        }
+    }
+
+    private void readDecimal(int start, int i) throws ParseException {
+        char[] chars = statementChars;
+        int[] types = characterTypes;
+        while (true) {
+            int t = types[i];
+            if (t != CHAR_DECIMAL && t != CHAR_VALUE) {
+                break;
+            }
+            i++;
+        }
+        if (chars[i] == 'E' || chars[i] == 'e') {
+            i++;
+            if (chars[i] == '+' || chars[i] == '-') {
+                i++;
+            }
+            if (types[i] != CHAR_VALUE) {
+                throw getSyntaxError();
+            }
+            while (types[++i] == CHAR_VALUE) {
+                // go until the first non-number
+            }
+        }
+        parseIndex = i;
+        String sub = statement.substring(start, i);
+        BigDecimal bd;
+        try {
+            bd = new BigDecimal(sub);
+        } catch (NumberFormatException e) {
+            throw new ParseException("Data conversion error converting " + sub + " to BigDecimal: " + e, parseIndex);
+        }
+        checkLiterals(false);
+
+        currentValue = valueFactory.createValue(bd);
+        currentTokenType = VALUE;
+    }
+
+    private ParseException getSyntaxError() {
+        if (expected == null || expected.isEmpty()) {
+            return getSyntaxError(null);
+        } else {
+            StringBuilder buff = new StringBuilder();
+            for (String exp : expected) {
+                if (buff.length() > 0) {
+                    buff.append(", ");
+                }
+                buff.append(exp);
+            }
+            return getSyntaxError(buff.toString());
+        }
+    }
+
+    private ParseException getSyntaxError(String expected) {
+        int index = Math.min(parseIndex, statement.length() - 1);
+        String query = statement.substring(0, index) + "(*)" + statement.substring(index).trim();
+        if (expected != null) {
+            query += "; expected: " + expected;
+        }
+        return new ParseException("Query:\n" + query, index);
+    }
+
+    /**
+     * Represents a column or a wildcard in a SQL expression.
+     * This class is temporarily used during parsing.
+     */
+    static class ColumnOrWildcard {
+        String selectorName;
+        String propertyName;
+        String columnName;
+    }
+
+    /**
+     * Get the selector name if only one selector exists in the query.
+     * If more than one selector exists, an exception is thrown.
+     *
+     * @return the selector name
+     */
+    private String getOnlySelectorName() throws ParseException {
+        if (selectors.size() > 1) {
+            throw getSyntaxError("Need to specify the selector name because the query contains more than one selector.");
+        }
+        return selectors.get(0).getSelectorName();
+    }
+
+    public static String escapeStringLiteral(String value) {
+        return '\'' + value.replace("'", "''") + '\'';
+    }
+
+    public void setAllowTextLiterals(boolean allowTextLiterals) {
+        this.allowTextLiterals = allowTextLiterals;
+    }
+
+    public void setAllowNumberLiterals(boolean allowNumberLiterals) {
+        this.allowNumberLiterals = allowNumberLiterals;
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Value.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Value.java?rev=1302927&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Value.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Value.java Tue Mar 20 15:05:04 2012
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.query;
+
+import java.math.BigDecimal;
+
+public class Value implements Comparable<Value> {
+
+    private final Object value;
+    private final int type;
+
+    public Value(Object value, int type) {
+        this.value = value;
+        this.type = type;
+    }
+
+    public String getString() {
+        return value.toString();
+    }
+
+    public int getType() {
+        return type;
+    }
+
+    public long getLong() {
+        // TODO convert?
+        return ((Long) value).longValue();
+    }
+
+    public double getDouble() {
+        // TODO convert?
+        return ((Double) value).doubleValue();
+    }
+
+    public boolean getBoolean() {
+        // TODO convert?
+        return ((Boolean) value).booleanValue();
+    }
+
+    public BigDecimal getDecimal() {
+        // TODO convert?
+        return (BigDecimal) value;
+    }
+
+    public String getBinary() {
+        // TODO convert?
+        return value.toString();
+    }
+
+    public String getDate() {
+        // TODO convert?
+        return value.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return type ^ value.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof Value)) {
+            return false;
+        }
+        Value v = (Value) o;
+        return type == v.type && value.equals(v.value);
+    }
+
+    @Override
+    public int compareTo(Value o) {
+        if (this == o) {
+            return 0;
+        }
+        if (type != o.type) {
+            // TODO convert?
+            return type - o.type;
+        }
+        switch (type) {
+        case PropertyType.LONG:
+            return ((Long) value).compareTo((Long) o.value);
+        case PropertyType.DOUBLE:
+            return ((Double) value).compareTo((Double) o.value);
+        case PropertyType.DECIMAL:
+            return ((BigDecimal) value).compareTo((BigDecimal) o.value);
+        case PropertyType.BOOLEAN:
+            return ((Boolean) value).compareTo((Boolean) o.value);
+        }
+        return value.toString().compareTo(o.toString());
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ValueFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ValueFactory.java?rev=1302927&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ValueFactory.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ValueFactory.java Tue Mar 20 15:05:04 2012
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.query;
+
+import java.math.BigDecimal;
+
+public class ValueFactory {
+
+    public Value createValue(String value) {
+        return new Value(value, PropertyType.STRING);
+    }
+
+    public Value createValue(BigDecimal value) {
+        return new Value(value, PropertyType.DECIMAL);
+    }
+
+    public Value createValue(double value) {
+        return new Value(value, PropertyType.DOUBLE);
+    }
+
+    public Value createValue(long value) {
+        return new Value(value, PropertyType.DECIMAL);
+    }
+
+    public Value createValue(boolean value) {
+        return new Value(value, PropertyType.BOOLEAN);
+    }
+
+    public Value createValue(String value, int type) {
+        return new Value(value, type);
+    }
+
+}