All Downloads are FREE. Search and download functionalities are using the official Maven repository.

play.db.SQLSplitter Maven / Gradle / Ivy

package play.db;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.NoSuchElementException;

public class SQLSplitter implements Iterable {

    /**
     * Skips the index past the quote.
     *
     * @param s
     *            The string
     * @param start
     *            The starting character of the quote.
     * @return The index that skips past the quote starting at start. If the quote does not start at that point, it
     *         simply returns start.
     */
    static int consumeQuote(CharSequence s, int start) {
        if (start >= s.length())
            return start;
        char ender;
        switch (s.charAt(start)) {
        case '\'':
            ender = '\'';
            break;
        case '"':
            ender = '"';
            break;
        case '[':
            ender = ']';
            break;

        case '`':
            ender = '`';
            break;
        case '$': {
            int quoteEnd = start + 1;
            for (; s.charAt(quoteEnd) != '$'; ++quoteEnd)
                if (quoteEnd >= s.length())
                    return quoteEnd;
            int i = quoteEnd + 1;
            while (i < s.length()) {
                if (s.charAt(i) == '$') {
                    boolean match = true;
                    for (int j = start; j <= quoteEnd && i < s.length(); ++j, ++i) {
                        if (s.charAt(i) != s.charAt(j)) {
                            match = false;
                            break;
                        }
                    }
                    if (match)
                        return i;
                } else
                    ++i;
            }
            return i;
        }

        default:
            return start;
        }

        boolean escaped = false;

        for (int i = start + 1; i < s.length(); ++i) {
            if (escaped) {
                escaped = false;
                continue;
            }
            char c = s.charAt(i);
            if (c == '\\')
                escaped = true;
            else if (c == ender)
                return i + 1;
        }
        return s.length();
    }

    static boolean isNewLine(char c) {
        return c == '\n' || c == '\r';
    }

    /**
     * Returns the index of the next line from a start location.
     */
    static int consumeTillNextLine(CharSequence s, int start) {
        while (start < s.length() && !isNewLine(s.charAt(start)))
            ++start;
        while (start < s.length() && isNewLine(s.charAt(start)))
            ++start;
        return start;
    }

    static boolean isNext(CharSequence s, int start, char c) {
        if (start + 1 < s.length())
            return s.charAt(start + 1) == c;
        return false;
    }

    /**
     * Skips the index past the comment.
     *
     * @param s
     *            The string
     * @param start
     *            The starting character of the comment
     * @return The index that skips past the comment starting at start. If the comment does not start at that point, it
     *         simply returns start.
     */
    static int consumeComment(CharSequence s, int start) {
        if (start >= s.length())
            return start;
        switch (s.charAt(start)) {
        case '-':
            if (isNext(s, start, '-'))
                return consumeTillNextLine(s, start + 2);
            else
                return start;

        case '#':
            return consumeTillNextLine(s, start);

        case '/':
            if (isNext(s, start, '*')) {
                start += 2;
                while (start < s.length()) {
                    if (s.charAt(start) == '*') {
                        ++start;
                        if (start < s.length() && s.charAt(start) == '/')
                            return start + 1;
                    } else
                        ++start;
                }
            }
            return start;

        case '{':
            while (start < s.length() && s.charAt(start) != '}')
                ++start;
            return start + 1;

        default:
            return start;
        }
    }

    static int consumeParentheses(CharSequence s, int start) {
        if (start >= s.length())
            return start;
        switch (s.charAt(start)) {
        case '(':
            ++start;
            while (start < s.length()) {
                if (s.charAt(start) == ')')
                    return start + 1;
                start = nextChar(s, start);
            }
            break;
        default:
            break;
        }
        return start;
    }

    static int nextChar(CharSequence sql, int start) {
        int i = consumeParentheses(sql, consumeComment(sql, consumeQuote(sql, start)));
        if (i == start)
            return Math.min(start + 1, sql.length());
        do {
            int j = consumeParentheses(sql, consumeComment(sql, consumeQuote(sql, i)));
            if (j == i)
                return i;
            i = j;
        } while (true);
    }

    /**
     * Splits the SQL "properly" based on semicolons. Respecting quotes and comments.
     * 
     * @param sql
     *            the SQL statement
     * @return List of all SQL statements
     */
    public static ArrayList splitSQL(CharSequence sql) {
        ArrayList ret = new ArrayList<>();
        for (CharSequence c : new SQLSplitter(sql))
            ret.add(c);
        return ret;
    }

    final CharSequence sql;

    public SQLSplitter(CharSequence sql) {
        this.sql = sql;
    }

    @Override
    public Iterator iterator() {
        return new Iterator() {
            int i = 0, prev = 0;

            @Override
            public boolean hasNext() {
                return prev < sql.length();
            }

            @Override
            public CharSequence next() {
                while (i < sql.length()) {
                    if (sql.charAt(i) == ';') {
                        ++i;
                        // check "double semicolon" -> used to escape a semicolon and avoid splitting
                        if ((i < sql.length() && sql.charAt(i) == ';')) {
                            ++i;
                        } else {
                            CharSequence ret = sql.subSequence(prev, i).toString().replace(";;", ";");
                            prev = i;
                            return ret;
                        }
                    }
                    i = nextChar(sql, i);
                }
                if (prev != i) {
                    CharSequence ret = sql.subSequence(prev, i).toString().replace(";;", ";");
                    prev = i;
                    return ret;
                }
                throw new NoSuchElementException();
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy