You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by th...@apache.org on 2007/10/12 16:05:38 UTC

svn commit: r584165 - in /jackrabbit/trunk/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/query/sql2/ test/java/org/apache/jackrabbit/core/query/sql2/ test/resources/org/apache/jackrabbit/core/query/ test/resources/org/apache/jackrabbit/core...

Author: thomasm
Date: Fri Oct 12 07:05:37 2007
New Revision: 584165

URL: http://svn.apache.org/viewvc?rev=584165&view=rev
Log:
JCR-1104: JSR 283 support / SQL2 parser (work in progress)

Added:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/sql2/
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/sql2/Parser.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/sql2/
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/sql2/ParserTest.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/query/
    jackrabbit/trunk/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/query/sql2/
    jackrabbit/trunk/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/query/sql2/test.sql2.txt   (with props)

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/sql2/Parser.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/sql2/Parser.java?rev=584165&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/sql2/Parser.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/sql2/Parser.java Fri Oct 12 07:05:37 2007
@@ -0,0 +1,957 @@
+/*
+ * 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.core.query.sql2;
+
+import org.apache.jackrabbit.core.query.jsr283.PreparedQuery;
+import org.apache.jackrabbit.core.query.jsr283.qom.BindVariableValue;
+import org.apache.jackrabbit.core.query.jsr283.qom.Column;
+import org.apache.jackrabbit.core.query.jsr283.qom.Constraint;
+import org.apache.jackrabbit.core.query.jsr283.qom.DynamicOperand;
+import org.apache.jackrabbit.core.query.jsr283.qom.JoinCondition;
+import org.apache.jackrabbit.core.query.jsr283.qom.Literal;
+import org.apache.jackrabbit.core.query.jsr283.qom.Ordering;
+import org.apache.jackrabbit.core.query.jsr283.qom.PropertyExistence;
+import org.apache.jackrabbit.core.query.jsr283.qom.PropertyValue;
+import org.apache.jackrabbit.core.query.jsr283.qom.QueryObjectModelConstants;
+import org.apache.jackrabbit.core.query.jsr283.qom.QueryObjectModelFactory;
+import org.apache.jackrabbit.core.query.jsr283.qom.Selector;
+import org.apache.jackrabbit.core.query.jsr283.qom.Source;
+import org.apache.jackrabbit.core.query.jsr283.qom.StaticOperand;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+import javax.jcr.query.InvalidQueryException;
+
+/**
+ * The SQL2 parser can convert a JCR-SQL2 query to a PreparedQuery.
+ */
+public class Parser {
+
+    // 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 expected;
+
+    // The bind variables
+    private HashMap bindVariables;
+    
+    // The list of selectors of this query
+    private ArrayList selectors;
+    
+    // SQL injection protection: if disabled, literals are not allowed
+    private boolean allowTextLiterals = true, allowNumberLiterals = true;
+
+    private QueryObjectModelFactory factory;
+    private ValueFactory valueFactory;
+
+    /**
+     * Create a new parser. A parser can be re-used, but it is not thread safe.
+     * 
+     * @param factory the query object model factory
+     * @param valueFactory the value factory
+     */
+    public Parser(QueryObjectModelFactory factory, ValueFactory valueFactory) {
+        this.factory = factory;
+        this.valueFactory = valueFactory;
+    }
+
+    /**
+     * Parse a JCR-SQL2 query and return the prepared query.
+     * 
+     * @param query the query string
+     * @return the prepared query
+     * @throws RepositoryException if parsing failed
+     */
+    // Page 125
+    public PreparedQuery createPreparedQuery(String query) throws RepositoryException {
+        initialize(query);
+        selectors = new ArrayList();
+        expected = new ArrayList();
+        bindVariables = new HashMap();
+        read();
+        read("SELECT");
+        ArrayList list = parseColumns();
+        read("FROM");
+        Source source = parseSource();
+        Column[] columnArray = resolveColumns(list);
+        Constraint constraint = null;
+        if (readIf("WHERE")) {
+            constraint = parseConstraint();
+        }
+        Ordering[] orderings = null;
+        if (readIf("ORDER")) {
+            read("BY");
+            orderings = parseOrder();
+        }
+        return factory.createQuery(source, constraint, orderings, columnArray);
+    }    
+
+    // Page 127
+    private Selector parseSelector() throws RepositoryException {
+        String nodeTypeName = readName();
+        if (readIf("AS")) {
+            String selectorName = readName();
+            return factory.selector(nodeTypeName, selectorName);
+        } else {
+            return factory.selector(nodeTypeName);
+        }
+    }
+    
+    // Page 128
+    private String readName() throws RepositoryException {
+        if (readIf("[")) {
+            if (currentTokenType == VALUE) {
+                String s = readString();
+                read("]");
+                return s;
+            } else {
+                int level = 1;
+                StringBuffer buff = new StringBuffer();
+                while (true) {
+                    if (isToken("]")) {
+                        if (--level <= 0) {
+                            read();
+                            break;
+                        }
+                    } else if (isToken("[")) {
+                        level++;
+                    }
+                    buff.append(readAny());
+                }
+                return buff.toString();
+            }
+        } else {
+            return readAny();
+        }
+    }
+    
+    // Page 129
+    private Source parseSource() throws RepositoryException {
+        Selector selector = parseSelector();
+        selectors.add(selector);
+        Source source = selector;
+        while (true) {
+            int type;
+            if (readIf("RIGHT")) {
+                read("OUTER");
+                type = QueryObjectModelConstants.JOIN_TYPE_RIGHT_OUTER;
+            } else if (readIf("LEFT")) {
+                read("OUTER");
+                type = QueryObjectModelConstants.JOIN_TYPE_LEFT_OUTER;
+            } else if (readIf("INNER")) {
+                type = QueryObjectModelConstants.JOIN_TYPE_INNER;
+            } else {
+                break;
+            }
+            read("JOIN");
+            selector = parseSelector();
+            selectors.add(selector);
+            read("ON");
+            JoinCondition on = parseJoinCondition();
+            source = factory.join(source, selector, type, on);
+        }
+        return source;
+    }
+    
+    // Page 130
+    private JoinCondition parseJoinCondition() throws RepositoryException {
+        boolean identifier = currentTokenType == IDENTIFIER;
+        String name = readName();
+        JoinCondition c;
+        if (identifier && readIf("(")) {
+            if ("ISSAMENODE".equals(name)) {
+                String selector1 = readName();
+                read(",");
+                String selector2 = readName();
+                if (readIf(",")) {
+                    c = factory.sameNodeJoinCondition(selector1, selector2, readPath());
+                } else {
+                    c = factory.sameNodeJoinCondition(selector1, selector2);
+                }
+            } else if ("ISCHILDNODE".equals(name)) {
+                String childSelector = readName();
+                read(",");
+                c = factory.childNodeJoinCondition(childSelector, readName());
+            } else if ("ISDESCENDANTNODE".equals(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());
+        }
+    }
+    
+    // Page 136
+    private Constraint parseConstraint() throws RepositoryException {
+        Constraint a = parseAnd();
+        while (readIf("OR")) {
+            a = factory.or(a, parseAnd());
+        }
+        return a;
+    }
+
+    private Constraint parseAnd() throws RepositoryException {
+        Constraint a = parseCondition();
+        while (readIf("AND")) {
+            a = factory.and(a, parseCondition());
+        }
+        return a;
+    }
+    
+    // Page 138
+    private Constraint parseCondition() throws RepositoryException {
+        Constraint a;
+        if (readIf("NOT")) {
+            a = parseConstraint();
+        } else if (readIf("(")) {
+            a = parseConstraint();
+            read(")");
+        } else if (currentTokenType == IDENTIFIER) {
+            String identifier = readName();
+            if (readIf("(")) {
+                a = parseConditionFuntionIf(identifier);
+                if (a == null) {
+                    DynamicOperand op = parseExpressionFunction(identifier);
+                    a = parseCondition(op);
+                }
+            } else if (readIf(".")) {
+                a = parseCondition(factory.propertyValue(identifier, readName()));
+            } else {
+                a = parseCondition(factory.propertyValue(identifier));
+            }
+        } else {
+            throw getSyntaxError();
+        }
+        return a;
+    }
+    
+    // Page 141
+    private Constraint parseCondition(DynamicOperand left) throws RepositoryException {
+        Constraint c;
+        if (readIf("=")) {
+            c = factory.comparison(left,
+                    QueryObjectModelConstants.OPERATOR_EQUAL_TO,
+                    parseStaticOperand());
+        } else if (readIf("<>")) {
+            c = factory.comparison(left,
+                    QueryObjectModelConstants.OPERATOR_NOT_EQUAL_TO,
+                    parseStaticOperand());
+        } else if (readIf("<")) {
+            c = factory.comparison(left,
+                    QueryObjectModelConstants.OPERATOR_LESS_THAN,
+                    parseStaticOperand());
+        } else if (readIf(">")) {
+            c = factory.comparison(left,
+                    QueryObjectModelConstants.OPERATOR_GREATER_THAN,
+                    parseStaticOperand());
+        } else if (readIf("<=")) {
+            c = factory.comparison(left,
+                    QueryObjectModelConstants.OPERATOR_LESS_THAN_OR_EQUAL_TO,
+                    parseStaticOperand());
+        } else if (readIf(">=")) {
+            c = factory
+                    .comparison(
+                            left,
+                            QueryObjectModelConstants.OPERATOR_GREATER_THAN_OR_EQUAL_TO,
+                            parseStaticOperand());
+        } else if (readIf("LIKE")) {
+            c = factory.comparison(left,
+                    QueryObjectModelConstants.OPERATOR_LIKE,
+                    parseStaticOperand());
+        } else if (readIf("IS")) {
+            boolean not = readIf("NOT");
+            read("NULL");
+            if (!(left instanceof PropertyValue)) {
+                this.getSyntaxError("propertyName (NOT NULL is only supported for properties)");
+            }
+            PropertyValue p = (PropertyValue) left;
+            c = getPropertyExistence(p);
+            if (!not) {
+                c = factory.not(c);
+            }
+        } else if (readIf("NOT")) {
+            if (readIf("IS")) {
+                read("NULL");
+                if (!(left instanceof PropertyValue)) {
+                    throw new RepositoryException("Only property values can be tested for NOT IS NULL; got: " + left.getClass().getName());
+                }
+                PropertyValue pv = (PropertyValue) left;
+                c = getPropertyExistence(pv);
+            } else {
+                read("LIKE");
+                c = factory.not(factory.comparison(left,
+                        QueryObjectModelConstants.OPERATOR_LIKE,
+                        parseStaticOperand()));
+            }
+        } else {
+            throw getSyntaxError();
+        }
+        return c;
+    }
+    
+    private PropertyExistence getPropertyExistence(PropertyValue p) throws InvalidQueryException, RepositoryException {
+        if (p.getSelectorName() == null) {
+            return factory.propertyExistence(p.getPropertyName());
+        } else {
+            return factory.propertyExistence(p.getSelectorName(), p.getPropertyName());
+        }
+    }
+    
+    // Page 144
+    private Constraint parseConditionFuntionIf(String functionName) throws RepositoryException {
+        Constraint c;
+        if ("CONTAINS".equals(functionName)) {
+            String name = readName();
+            if (readIf(".")) {
+                if (readIf("*")) {
+                    read(",");
+                    c = factory.fullTextSearch(name, null, readString());
+                } else {
+                    String selector = name;
+                    name = readName();
+                    read(",");
+                    c = factory.fullTextSearch(selector, name, readString());
+                }
+            } else {
+                read(",");
+                c = factory.fullTextSearch(name, readString());
+            }
+        } else if ("ISSAMENODE".equals(functionName)) {
+            String name = readName();
+            if (readIf(",")) {
+                c = factory.sameNode(name, readPath());
+            } else {
+                c = factory.sameNode(name);
+            }
+        } else if ("ISCHILDNODE".equals(functionName)) {
+            String name = readName();
+            if (readIf(",")) {
+                c = factory.childNode(name, readPath());
+            } else {
+                c = factory.childNode(name);
+            }
+        } else if ("ISDESCENDANTNODE".equals(functionName)) {
+            String name = readName();
+            if (readIf(",")) {
+                c = factory.descendantNode(name, readPath());
+            } else {
+                c = factory.descendantNode(name);
+            }
+        } else {
+            return null;
+        }
+        read(")");
+        return c;
+    }
+    
+    // Page 148
+    private String readPath() throws RepositoryException {
+        return readName();
+    }
+    
+    // Page 149
+    private DynamicOperand parseDynamicOperand() throws RepositoryException {
+        boolean identifier = currentTokenType == IDENTIFIER;        
+        String name = readName();
+        if (identifier && readIf("(")) {
+            return parseExpressionFunction(name);
+        } else {
+            return parsePropertyValue(name);
+        }
+    }
+    
+    private DynamicOperand parseExpressionFunction(String functionName) throws RepositoryException {
+        DynamicOperand op;
+        if ("LENGTH".equals(functionName)) {
+            op = factory.length(parsePropertyValue(readName()));
+        } else if ("NAME".equals(functionName)) {
+            if (isToken(")")) {
+                op = factory.nodeName();
+            } else {
+                op = factory.nodeName(readName());
+            }
+        } else if ("LOCALNAME".equals(functionName)) {
+            if (isToken(")")) {
+                op = factory.nodeLocalName();
+            } else {
+                op = factory.nodeLocalName(readName());
+            }
+        } else if ("SCORE".equals(functionName)) {
+            if (isToken(")")) {
+                op = factory.fullTextSearchScore();
+            } else {
+                op = factory.fullTextSearchScore(readName());
+            }
+        } else if ("LOWER".equals(functionName)) {
+            op = factory.lowerCase(parseDynamicOperand());
+        } else if ("UPPER".equals(functionName)) {
+            op = factory.upperCase(parseDynamicOperand());
+        } else {
+            throw getSyntaxError("LENGTH, NAME, LOCALNAME, SCORE, LOWER, or UPPER");
+        }
+        read(")");
+        return op;
+    }
+    
+    // Page 150
+    private PropertyValue parsePropertyValue(String name) throws RepositoryException {
+        if (readIf(".")) {
+            return factory.propertyValue(name, readName());
+        } else {
+            return factory.propertyValue(name);
+        }
+    }
+    
+    // Page 155
+    private StaticOperand parseStaticOperand() throws RepositoryException {
+        if (currentTokenType == PLUS) {
+            read();
+        } else if (currentTokenType == MINUS) {
+            read();
+            if (currentTokenType != VALUE) {
+                throw getSyntaxError("number");
+            }
+            if (currentValue.getType() == PropertyType.LONG) {
+                currentValue = valueFactory.createValue(-currentValue.getLong());
+            } else if (currentValue.getType() == PropertyType.DOUBLE) {
+                currentValue = valueFactory.createValue(-currentValue.getDouble());
+            } else {
+                // TODO decimal
+                throw getSyntaxError("number");
+            }
+        } 
+        if (currentTokenType == VALUE) {
+            Literal literal = factory.literal(currentValue);
+            read();
+            return literal;
+        } else if (currentTokenType == PARAMETER) {
+            read();
+            String name = readName();
+            BindVariableValue var = (BindVariableValue) bindVariables.get(name);
+            if (var == null) {
+                var = factory.bindVariable(name);
+                bindVariables.put(name, var);
+            }
+            return var;
+        } else if (readIf("TRUE")) {
+            Literal literal = factory.literal(valueFactory.createValue(true));
+            return literal;
+        } else if (readIf("FALSE")) {
+            Literal literal = factory.literal(valueFactory.createValue(false));
+            return literal;
+        } else {
+            throw getSyntaxError("static operand");
+        }
+    }
+
+    // Page 157
+    private Ordering[] parseOrder() throws RepositoryException {
+        ArrayList orderList = new ArrayList();
+        do {
+            Ordering ordering;
+            DynamicOperand op = parseDynamicOperand();
+            if (readIf("DESC")) {
+                ordering = factory.descending(op);
+            } else {
+                readIf("ASC");
+                ordering = factory.ascending(op);
+            }
+            orderList.add(ordering);
+        } while (readIf(","));
+        Ordering[] orderings = new Ordering[orderList.size()];
+        orderList.toArray(orderings);
+        return orderings;
+    }
+    
+    // Page 159
+    private ArrayList parseColumns() throws RepositoryException {
+        ArrayList list = new ArrayList();        
+        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 Column[] resolveColumns(ArrayList list) throws RepositoryException {
+        ArrayList columns = new ArrayList();
+        for (int i = 0; i < list.size(); i++) {
+            ColumnOrWildcard c = (ColumnOrWildcard) list.get(i);
+            if (c.propertyName == null) {
+                for (int j = 0; j < selectors.size(); j++) {
+                    Selector selector = (Selector) selectors.get(j);
+                    if (c.selectorName == null
+                            || c.selectorName
+                                    .equals(selector.getSelectorName())) {
+                        Column column = factory.column(selector
+                                .getSelectorName(), null, null);
+                        columns.add(column);
+                    }
+                }
+            } else {
+                Column column;
+                if (c.selectorName != null) {
+                    column = factory.column(c.selectorName, c.propertyName, c.columnName);
+                } else if (c.columnName != null) {
+                    column = factory.column(c.propertyName, c.columnName);
+                } else {
+                    column = factory.column(c.propertyName);
+                }
+                columns.add(column);
+            }
+        }
+        Column[] array = new Column[columns.size()];
+        columns.toArray(array);
+        return array;
+    }
+    
+    private boolean readIf(String token) throws RepositoryException {
+        if (isToken(token)) {
+            read();
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isToken(String token) {
+        boolean result = token.equals(currentToken) && !currentTokenQuoted;
+        if (result) {
+            return true;
+        }
+        addExpected(token);
+        return false;
+    }
+
+    private void read(String expected) throws RepositoryException {
+        if (!expected.equals(currentToken) || currentTokenQuoted) {
+            throw getSyntaxError(expected);
+        }
+        read();
+    }
+
+    private String readAny() throws RepositoryException {
+        if (currentTokenType == END) {
+            throw getSyntaxError("a token");
+        }
+        String s;
+        if (currentTokenType == VALUE) {
+            s = currentValue.getString();
+        } else {
+            s = currentToken;
+        }
+        read();
+        return s;
+    }
+
+    private String readString() throws RepositoryException {
+        if (currentTokenType != VALUE) {
+            throw getSyntaxError("string value");
+        }
+        String s = currentValue.getString();
+        read();
+        return s;
+    }    
+
+    private void addExpected(String token) {
+        if (expected != null) {
+            expected.add(token);
+        }
+    }
+
+    private void initialize(String query) throws InvalidQueryException {
+        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);
+        boolean changed = false;
+        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 = types[i] = CHAR_STRING;
+                startLoop = i;
+                while (command[++i] != '\'') {
+                    checkRunOver(i, len, startLoop);
+                }
+                break;
+            case '\"':
+                type = 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') {
+                    command[i] = (char) (c - ('a' - 'A'));
+                    changed = true;
+                    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;
+                        char u = Character.toUpperCase(c);
+                        if (u != c) {
+                            command[i] = u;
+                            changed = true;
+                        }
+                    }
+                }
+            }
+            types[i] = (byte) type;
+        }
+        statementChars = command;
+        types[len] = CHAR_END;
+        characterTypes = types;
+        if (changed) {
+            statement = new String(command);
+        }
+        parseIndex = 0;
+    }
+
+    private void checkRunOver(int i, int len, int startLoop) throws InvalidQueryException {
+        if (i >= len) {
+            parseIndex = startLoop;
+            throw getSyntaxError();
+        }
+    }
+    
+    private void read() throws RepositoryException {
+        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.length() == 0) {
+                throw getSyntaxError();
+            }
+            currentTokenType = IDENTIFIER;
+            parseIndex = i;
+            return;
+        case CHAR_QUOTED: {
+            String result = null;
+            while (true) {
+                for (int begin = i;; i++) {
+                    if (chars[i] == '\"') {
+                        if (result == null) {
+                            result = statement.substring(begin, i);
+                        } else {
+                            result += statement.substring(begin - 1, i);
+                        }
+                        break;
+                    }
+                }
+                if (chars[++i] != '\"') {
+                    break;
+                }
+                i++;
+            }
+            currentToken = result;
+            parseIndex = i;
+            currentTokenQuoted = true;
+            currentTokenType = IDENTIFIER;
+            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') {
+                        readDecimal(start, i);
+                        break;
+                    }
+                    checkLiterals(false);
+                    currentValue = valueFactory.createValue((int) 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: {
+            String result = null;
+            while (true) {
+                for (int begin = i;; i++) {
+                    if (chars[i] == '\'') {
+                        if (result == null) {
+                            result = statement.substring(begin, i);
+                        } else {
+                            result += statement.substring(begin - 1, i);
+                        }
+                        break;
+                    }
+                }
+                if (chars[++i] != '\'') {
+                    break;
+                }
+                i++;
+            }
+            currentToken = "'";
+            checkLiterals(false);
+            currentValue = valueFactory.createValue(result);
+            parseIndex = i;
+            currentTokenType = VALUE;
+            return;
+        }
+        case CHAR_END:
+            currentToken = "";
+            currentTokenType = END;
+            parseIndex = i;
+            return;
+        default:
+            throw getSyntaxError();
+        }
+    }
+    
+    private void checkLiterals(boolean text) throws InvalidQueryException {
+        if (text && !allowTextLiterals || (!text && !allowNumberLiterals)) {
+            throw getSyntaxError("bind variable (literals of this type not allowed)");
+        }
+    }
+
+    private void readDecimal(int start, int i) throws RepositoryException {
+        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') {
+            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 InvalidQueryException("Data conversion error converting " + sub + " to BigDecimal: " + e);
+        }
+        checkLiterals(false);
+        // TODO BigDecimal or double?
+        currentValue = valueFactory.createValue(bd.doubleValue());
+        currentTokenType = VALUE;
+    }
+    
+    private InvalidQueryException getSyntaxError() {
+        if (expected == null || expected.size() == 0) {
+            return getSyntaxError(null);
+        } else {
+            StringBuffer buff = new StringBuffer();
+            for (int i = 0; i < expected.size(); i++) {
+                if (i > 0) {
+                    buff.append(", ");
+                }
+                buff.append(expected.get(i));
+            }
+            return getSyntaxError(buff.toString());
+        }
+    }
+    
+    private InvalidQueryException 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 InvalidQueryException("Query:\n" + query);
+    }    
+    
+    private static class ColumnOrWildcard {
+        String selectorName;
+        String propertyName;
+        String columnName;
+    }
+    
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/sql2/Parser.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/sql2/ParserTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/sql2/ParserTest.java?rev=584165&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/sql2/ParserTest.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/sql2/ParserTest.java Fri Oct 12 07:05:37 2007
@@ -0,0 +1,123 @@
+/*
+ * 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.core.query.sql2;
+
+import org.apache.jackrabbit.core.WorkspaceImpl;
+import org.apache.jackrabbit.core.query.QueryManagerImpl;
+import org.apache.jackrabbit.core.query.qom.QueryObjectModelFactoryImpl;
+import org.apache.jackrabbit.test.api.query.AbstractQueryTest;
+
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.util.Random;
+
+import javax.jcr.NamespaceException;
+import javax.jcr.RepositoryException;
+import javax.jcr.query.InvalidQueryException;
+
+/**
+ * This class runs function tests on the JCR-SQL2 parser.
+ */
+public class ParserTest extends AbstractQueryTest {
+
+    protected Parser parser;
+
+    protected Random random = new Random();
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        WorkspaceImpl wsp = (WorkspaceImpl) superuser.getWorkspace();
+        QueryManagerImpl qm = (QueryManagerImpl) wsp.getQueryManager();
+        QueryObjectModelFactoryImpl factory = (QueryObjectModelFactoryImpl) qm
+                .getQOMFactory();
+        parser = new Parser(factory, superuser.getValueFactory());
+    }
+
+    private LineNumberReader openScript(String name) {
+        return new LineNumberReader(new InputStreamReader(getClass()
+                .getResourceAsStream(name)));
+    }
+
+    public void testParseScript() throws Exception {
+        LineNumberReader reader = openScript("test.sql2.txt");
+        while (true) {
+            String line = reader.readLine();
+            if (line == null) {
+                break;
+            }
+            line = line.trim();
+            if (line.length() == 0 || line.startsWith("#")) {
+                continue;
+            }
+            // System.out.println(line);
+            try {
+                parser.createPreparedQuery(line);
+                fuzz(line);
+            } catch (RepositoryException e) {
+                line = reader.readLine();
+                if (line == null || !line.startsWith("> exception")) {
+                    e.printStackTrace();
+                    assertTrue("Unexpected exception for query " + line + ": "
+                            + e, false);
+                }
+            }
+        }
+        reader.close();
+    }
+
+    public void fuzz(String query) throws Exception {
+        for (int i = 0; i < 100; i++) {
+            StringBuffer buff = new StringBuffer(query);
+            int changes = 1 + (int) Math.abs(random.nextGaussian() * 2);
+            for (int j = 0; j < changes; j++) {
+                char newChar;
+                if (random.nextBoolean()) {
+                    String s = "<>_.+\"*%&/()=?[]{}_:;,.-1234567890.qersdf";
+                    newChar = s.charAt(random.nextInt(s.length()));
+                } else {
+                    newChar = (char) random.nextInt(255);
+                }
+                int pos = random.nextInt(buff.length());
+                if (random.nextBoolean()) {
+                    // 50%: change one character
+                    buff.setCharAt(pos, newChar);
+                } else {
+                    if (random.nextBoolean()) {
+                        // 25%: delete one character
+                        buff.deleteCharAt(pos);
+                    } else {
+                        // 25%: insert one character
+                        buff.insert(pos, newChar);
+                    }
+                }
+            }
+            String q = buff.toString();
+            try {
+                parser.createPreparedQuery(q);
+            } catch (InvalidQueryException e) {
+                // OK
+            } catch (NamespaceException e) {
+                // OK?
+            } catch (Throwable t) {
+                t.printStackTrace();
+                assertTrue("Unexpected exception for query " + q + ": " + t,
+                        false);
+            }
+        }
+    }
+
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/sql2/ParserTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/query/sql2/test.sql2.txt
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/query/sql2/test.sql2.txt?rev=584165&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/query/sql2/test.sql2.txt (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/query/sql2/test.sql2.txt Fri Oct 12 07:05:37 2007
@@ -0,0 +1,168 @@
+#page 125
+select * from test
+select * from test where id=1
+select * from test where id=1 order by id
+select * from test order by id
+
+#page 126
+select * from test as t
+select * from ["Test"]
+select * from [test]
+select * from [test] as [t]
+select * from test as ["t"]
+select * from ["test"] as ["t"]
+
+#page 129
+select * from parent inner join child on parent.id=child.parentid
+select * from parent as p inner join child as c on p.id=c.parentid
+select * from parent as p inner join child as c on p.id=c.parentid
+select * from parent as p left outer join child as c on p.id=c.parentid
+select * from parent as p right outer join child as c on p.id=c.parentid
+
+#page 133
+select * from parent as p inner join child as c on issamenode(p, c)
+select * from parent as p inner join child as c on issamenode(p, c, a)
+select * from parent as p inner join child as c on issamenode(p, c, [/a/b/c])
+select * from parent as p inner join child as c on issamenode(p, c, ['/a/b/c'])
+
+#page 134
+select * from parent as p inner join child as c on ischildnode(p, c)
+
+#page 135
+select * from parent as p inner join child as c on isdescendantnode(p, c)
+select * from parent as p right outer join child as c on p.id=c.parentid inner join other as x on p.id = x.id
+
+#page 136
+select * from test where id<1 and id>1
+select * from test where id=2 or name='Hello'
+
+#page 138
+select * from test where not id=2
+select * from test where not (id=2 and name='Hello')
+select * from test where id=2 or not (name='Hello' and id=3)
+
+#page 141
+select * from test where id<=2 or id>=3 and name<'a' or name>'c'
+select * from test where id<>2
+select * from test where name like 'H%'
+
+#page 142
+select * from test where name is not null
+select * from test as t where t.name is not null and t.id<>0
+select * from test as t where not t.name is not null 
+select * from test as t where t.name is null 
+select * from test as t where not t.name is null
+
+#page 144
+#TODO: is the full text expression quoted or not?
+select * from test where contains(name, 'hello -world')
+select * from test as t where contains(t.*, 'hello -world')
+select * from test as t where contains([t].name, 'hello -world')
+
+#page 145
+select * from test where issamenode([a/b/c])
+select * from test as a where issamenode(a)
+select * from test as x where issamenode(x, [a[2]/b/c])
+
+#page 146
+select * from test where ischildnode([a[1]/b])
+select * from test as a where ischildnode(a)
+select * from test as x where ischildnode(x, [/])
+select * from test as x where ischildnode(x, ['/a[1]'])
+
+#page 147
+select * from test where ISDESCENDANTNODE([/a[1]])
+select * from test as a where ISDESCENDANTNODE(a)
+select * from test as x where ISDESCENDANTNODE(x, [a/b/c])
+
+#TODO page 148: does simple path include '/'? 
+# simplePath ::= /* A JCR Name that contains only SQL-legal characters */  
+
+#page 150
+select * from test where length(name)=5
+select * from test as t where length(t.name)=5
+
+#page 151
+select * from test where name()='test'
+select * from test as x where name(x)='test'
+
+#page 152
+select * from test where localname()='test'
+select * from test as x where localname(x)='test'
+
+#page 153
+select * from test where score()>4
+select * from test as x where score(x)<1
+
+#page 153
+select * from test where lower(name)='test'
+select * from test where lower(upper(name))='test'
+select * from test where lower(localname(name))='test'
+select * from test where lower(name(name))='test'
+select * from test as x where lower(x.name)='test'
+
+#page 154
+select * from test where upper(name)='test'
+select * from test where upper(lower(name))='test'
+select * from test where upper(localname(name))='test'
+select * from test where upper(name(name))='test'
+select * from test as x where upper(x.name)='test'
+
+#page 155
+select * from test where amount=0.01
+select * from test where amount=10.
+select * from test where amount=.01
+select * from test where amount=.01e-1
+select * from test where amount=-.01e1
+select * from test where amount=-0.01e1
+select * from test where amount=+10
+select * from test where amount=-10e10
+select * from test where amount=+10e-10
+select * from test where amount=+10e+10
+select * from test where active=true
+select * from test where active=false
+select * from test where name='test''test'
+select * from test where name=''''
+
+select * from test where name=$name
+select * from test where name=$x and id=$y
+select * from test where name=$x14
+select * from test where name=$_
+select * from test where name=$äöü
+select * from test where name=$äöü
+
+#page 157
+select * from test order by name
+select * from test order by name asc
+select * from test order by name desc
+select * from test order by id, name
+select * from test order by id, name, id, name
+select * from test order by id desc, name asc, id, name desc
+select * from test order by id desc, name asc, id asc
+
+#page 159
+select name from test
+select id, name from test
+select x.id from test as x
+select x.id, name from test as x
+select x.id, y.id from test as x inner join test as y on x.id=y.id
+select id as i from test as x inner join test as y on x.id=y.id
+select x.id as i, name as n from test as x inner join test as y on x.id=y.id
+select id, name as n from test as x inner join test as y on x.id=y.id
+select x.* from test as x
+select x.*, y.* from test as x inner join test as y on x.id=y.id
+select x.*, id as i, y.*, name from test as x inner join test as y on x.id=y.id
+
+#errors
+select * from parent as p inner join child as c on issamenode(p, c, a/b)
+> exception
+select * from parent as p inner join child as c on issamenode(p, c, d, e)
+> exception
+select * from 
+> exception
+select * from parent as p inner join child as c on ischildnode(p, c, a)
+> exception
+select * from parent as p inner join child as c on isdescendantnode(p)
+> exception
+select * from parent as p inner join child as c on isdescendantnode(a, b, c)
+> exception

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/query/sql2/test.sql2.txt
------------------------------------------------------------------------------
    svn:eol-style = native