Package groovy.sql

Source Code of groovy.sql.ExtractIndexAndSql

/*
* Copyright 2003-2014 the original author or authors.
*
* Licensed 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 groovy.sql;

import groovy.lang.Tuple;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Extracts and indexes named parameters from a sql string.
*
* This class is package-private scoped and is only intended for internal use.
*
* @see groovy.sql.Sql
*/
class ExtractIndexAndSql {

    private static final Pattern NAMED_QUERY_PATTERN = Pattern.compile("(?<!:)(:)(\\w+)|\\?(\\d*)(?:\\.(\\w+))?");
    private static final char QUOTE = '\'';

    private final String sql;
    private List<Tuple> indexPropList;
    private String newSql;

    /**
     * Used to track the current position within the sql while parsing
     */
    private int index = 0;

    /**
     * Static factory method used to create a new instance.  Since parsing of the input
     * is required, this ensures the object is fully initialized.
     *
     * @param sql statement to be parsed
     * @return an instance of {@link ExtractIndexAndSql}
     */
    static ExtractIndexAndSql from(String sql) {
        return new ExtractIndexAndSql(sql).invoke();
    }

    /**
     * Checks a sql statement to determine whether it contains parameters.
     *
     * @param sql statement
     * @return {@code true} if the statement contains named parameters, otherwise {@code false}
     */
    static boolean hasNamedParameters(String sql) {
        return NAMED_QUERY_PATTERN.matcher(sql).find();
    }

    private ExtractIndexAndSql(String sql) {
        this.sql = sql;
    }

    List<Tuple> getIndexPropList() {
        return indexPropList;
    }

    String getNewSql() {
        return newSql;
    }

    private ExtractIndexAndSql invoke() {
        indexPropList = new ArrayList<Tuple>();
        StringBuilder sb = new StringBuilder();
        StringBuilder currentChunk = new StringBuilder();
        while (index < sql.length()) {
            switch (sql.charAt(index)) {
                case QUOTE:
                    sb.append(adaptForNamedParams(currentChunk.toString(), indexPropList));
                    currentChunk = new StringBuilder();
                    appendToEndOfString(sb);
                    break;
                case '-':
                    if (next() == '-') {
                        sb.append(adaptForNamedParams(currentChunk.toString(), indexPropList));
                        currentChunk = new StringBuilder();
                        appendToEndOfLine(sb);
                    } else {
                        currentChunk.append(sql.charAt(index));
                    }
                    break;
                case '/':
                    if (next() == '*') {
                        sb.append(adaptForNamedParams(currentChunk.toString(), indexPropList));
                        currentChunk = new StringBuilder();
                        appendToEndOfComment(sb);
                    } else {
                        currentChunk.append(sql.charAt(index));
                    }
                    break;
                default:
                    currentChunk.append(sql.charAt(index));
            }
            index++;
        }
        sb.append(adaptForNamedParams(currentChunk.toString(), indexPropList));
        newSql = sb.toString();
        return this;
    }

    private void appendToEndOfString(StringBuilder buffer) {
        buffer.append(QUOTE);
        int startQuoteIndex = index;
        ++index;
        boolean foundClosingQuote = false;
        while (index < sql.length()) {
            char c = sql.charAt(index);
            buffer.append(c);
            if (c == QUOTE && next() != QUOTE) {
                if (startQuoteIndex == (index - 1)) {   // empty quote ''
                    foundClosingQuote = true;
                    break;
                }
                int previousQuotes = countPreviousRepeatingChars(QUOTE);
                if (previousQuotes == 0 ||
                        (previousQuotes % 2 == 0 && (index - previousQuotes) != startQuoteIndex) ||
                        (previousQuotes % 2 != 0 && (index - previousQuotes) == startQuoteIndex)) {
                    foundClosingQuote = true;
                    break;
                }
            }
            ++index;
        }
        if (!foundClosingQuote) {
            throw new IllegalStateException("Failed to process query. Unterminated ' character?");
        }
    }

    private int countPreviousRepeatingChars(char c) {
        int pos = index - 1;
        while (pos >= 0) {
            if (sql.charAt(pos) != c) {
                break;
            }
            --pos;
        }
        return (index - 1) - pos;
    }

    private void appendToEndOfComment(StringBuilder buffer) {
        while (index < sql.length()) {
            char c = sql.charAt(index);
            buffer.append(c);
            if (c == '*' && next() == '/') {
                buffer.append('/');
                ++index;
                break;
            }
            ++index;
        }
    }

    private void appendToEndOfLine(StringBuilder buffer) {
        while (index < sql.length()) {
            char c = sql.charAt(index);
            buffer.append(c);
            if (c == '\n' || c == '\r') {
                break;
            }
            ++index;
        }
    }

    private char next() {
        return ((index + 1) < sql.length()) ? sql.charAt(index + 1) : '\0';
    }

    private String adaptForNamedParams(String sql, List<Tuple> indexPropList) {
        StringBuilder newSql = new StringBuilder();
        int txtIndex = 0;

        Matcher matcher = NAMED_QUERY_PATTERN.matcher(sql);
        while (matcher.find()) {
            newSql.append(sql.substring(txtIndex, matcher.start())).append('?');
            String indexStr = matcher.group(1);
            if (indexStr == null) indexStr = matcher.group(3);
            int index = (indexStr == null || indexStr.length() == 0 || ":".equals(indexStr)) ? 0 : new Integer(indexStr) - 1;
            String prop = matcher.group(2);
            if (prop == null) prop = matcher.group(4);
            indexPropList.add(new Tuple(new Object[]{index, prop == null || prop.length() == 0 ? "<this>" : prop}));
            txtIndex = matcher.end();
        }
        newSql.append(sql.substring(txtIndex)); // append ending SQL after last param.
        return newSql.toString();
    }

}
TOP

Related Classes of groovy.sql.ExtractIndexAndSql

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.