<commit_before>/*
 * 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.spi.commons.query.sql2;

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;
import javax.jcr.query.qom.BindVariableValue;
import javax.jcr.query.qom.Column;
import javax.jcr.query.qom.Constraint;
import javax.jcr.query.qom.DynamicOperand;
import javax.jcr.query.qom.JoinCondition;
import javax.jcr.query.qom.Literal;
import javax.jcr.query.qom.Ordering;
import javax.jcr.query.qom.PropertyExistence;
import javax.jcr.query.qom.PropertyValue;
import javax.jcr.query.qom.QueryObjectModel;
import javax.jcr.query.qom.QueryObjectModelConstants;
import javax.jcr.query.qom.QueryObjectModelFactory;
import javax.jcr.query.qom.Selector;
import javax.jcr.query.qom.Source;
import javax.jcr.query.qom.StaticOperand;

/**
 * The SQL2 parser can convert a JCR-SQL2 query to a QueryObjectModel.
 */
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 query object model
     *
     * @param query the query string
     * @return the query object model
     * @throws RepositoryException if parsing failed
     */
    // Page 125
    public QueryObjectModel createQueryObjectModel(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, null /* TODO */);
        }
    }

    // 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) {
            String type;
            if (readIf("RIGHT")) {
                read("OUTER");
                type = QueryObjectModelConstants.JCR_JOIN_TYPE_RIGHT_OUTER;
            } else if (readIf("LEFT")) {
                read("OUTER");
                type = QueryObjectModelConstants.JCR_JOIN_TYPE_LEFT_OUTER;
            } else if (readIf("INNER")) {
                type = QueryObjectModelConstants.JCR_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, null /* TODO */);
                }
            } 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, null /* TODO */));
            }
        } else {
            throw getSyntaxError();
        }
        return a;
    }

    // Page 141
    private Constraint parseCondition(DynamicOperand left) throws RepositoryException {
        Constraint c;
        if (readIf("=")) {
            c = factory.comparison(left,
                    QueryObjectModelConstants.JCR_OPERATOR_EQUAL_TO,
                    parseStaticOperand());
        } else if (readIf("<>")) {
            c = factory.comparison(left,
                    QueryObjectModelConstants.JCR_OPERATOR_NOT_EQUAL_TO,
                    parseStaticOperand());
        } else if (readIf("<")) {
            c = factory.comparison(left,
                    QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN,
                    parseStaticOperand());
        } else if (readIf(">")) {
            c = factory.comparison(left,
                    QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN,
                    parseStaticOperand());
        } else if (readIf("<=")) {
            c = factory.comparison(left,
                    QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN_OR_EQUAL_TO,
                    parseStaticOperand());
        } else if (readIf(">=")) {
            c = factory
                    .comparison(
                            left,
                            QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN_OR_EQUAL_TO,
                            parseStaticOperand());
        } else if (readIf("LIKE")) {
            c = factory.comparison(left,
                    QueryObjectModelConstants.JCR_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.JCR_OPERATOR_LIKE,
                        parseStaticOperand()));
            }
        } else {
            throw getSyntaxError();
        }
        return c;
    }<commit_msg>JCR-2094: Use an enumeration for QOM operators<commit_after>/*
 * 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.spi.commons.query.sql2;

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;
import javax.jcr.query.qom.BindVariableValue;
import javax.jcr.query.qom.Column;
import javax.jcr.query.qom.Constraint;
import javax.jcr.query.qom.DynamicOperand;
import javax.jcr.query.qom.JoinCondition;
import javax.jcr.query.qom.Literal;
import javax.jcr.query.qom.Ordering;
import javax.jcr.query.qom.PropertyExistence;
import javax.jcr.query.qom.PropertyValue;
import javax.jcr.query.qom.QueryObjectModel;
import javax.jcr.query.qom.QueryObjectModelConstants;
import javax.jcr.query.qom.QueryObjectModelFactory;
import javax.jcr.query.qom.Selector;
import javax.jcr.query.qom.Source;
import javax.jcr.query.qom.StaticOperand;

import org.apache.jackrabbit.spi.commons.query.qom.Operator;

/**
 * The SQL2 parser can convert a JCR-SQL2 query to a QueryObjectModel.
 */
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 query object model
     *
     * @param query the query string
     * @return the query object model
     * @throws RepositoryException if parsing failed
     */
    // Page 125
    public QueryObjectModel createQueryObjectModel(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, null /* TODO */);
        }
    }

    // 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) {
            String type;
            if (readIf("RIGHT")) {
                read("OUTER");
                type = QueryObjectModelConstants.JCR_JOIN_TYPE_RIGHT_OUTER;
            } else if (readIf("LEFT")) {
                read("OUTER");
                type = QueryObjectModelConstants.JCR_JOIN_TYPE_LEFT_OUTER;
            } else if (readIf("INNER")) {
                type = QueryObjectModelConstants.JCR_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, null /* TODO */);
                }
            } 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, null /* TODO */));
            }
        } else {
            throw getSyntaxError();
        }
        return a;
    }

    // Page 141
    private Constraint parseCondition(DynamicOperand left) throws RepositoryException {
        Constraint c;
        if (readIf("=")) {
            c = Operator.EQ.comparison(factory, left, parseStaticOperand());
        } else if (readIf("<>")) {
            c = Operator.NE.comparison(factory, left, parseStaticOperand());
        } else if (readIf("<")) {
            c = Operator.LT.comparison(factory, left, parseStaticOperand());
        } else if (readIf(">")) {
            c = Operator.GT.comparison(factory, left, parseStaticOperand());
        } else if (readIf("<=")) {
            c = Operator.LE.comparison(factory, left, parseStaticOperand());
        } else if (readIf(">=")) {
            c = Operator.GE.comparison(factory, left, parseStaticOperand());
        } else if (readIf("LIKE")) {
            c = Operator.LIKE.comparison(factory, left, 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(Operator.LIKE.comparison(
                        factory, left, parseStaticOperand()));
            }
        } else {
            throw getSyntaxError();
        }
        return c;
    }
{'fraction_non_alphanumeric': 0.10091365544489259, 'fraction_numerical': 0.005844102395258869, 'mean_word_length': 1.8965311717725593, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5836495', 'n_tokens_mistral': 6340, 'n_tokens_neox': 6057, 'n_words': 1711}