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

org.codehaus.mojo.sql.SqlSplitter Maven / Gradle / Ivy

The newest version!
package org.codehaus.mojo.sql;

/*
 * 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.    
 */

import org.codehaus.plexus.util.StringUtils;

/**
 * Utility class to split a long sql batch script into single SQL commands.
 */
public final class SqlSplitter
{
    private SqlSplitter()
    {
        // hide utility class constructor
    }

    /**
     * Value indicating the sql has no end-delimiter like i.e. the semicolon.
     */
    public static final int NO_END = -1;

    /**
     * parsed sql started a single quote static text which continues on the next line (did not end)
     */
    public static final int OVERFLOW_SINGLE_QUOTE = -2;

    /**
     * parsed sql started a double quote static text which continues on the next line (did not end)
     */
    public static final int OVERFLOW_DOUBLE_QUOTE = -4;

    /**
     * parsed sql started a comment with /_* which continues on the next line (did not end)
     */
    public static final int OVERFLOW_COMMENT = -8;

    /**
     * Check if the given sql line contains a delimiter representing the end of the command. Please note that we do
     * not fully parse the SQL, so if we get a malformed statement, we cannot detect it.
     * 
     * @param line to parse
     * @param delimiter which should be used to split SQL commands
     * @param overflowValue 0=none, {@link SqlSplitter#OVERFLOW_COMMENT}, {@link SqlSplitter#OVERFLOW_SINGLE_QUOTE} or
     *            {@link SqlSplitter#OVERFLOW_DOUBLE_QUOTE}
     * @return position after the end character if the given line contains the end of a SQL script,
     *         {@link SqlSplitter#NO_END} if it doesn't contain an end char. {@link SqlSplitter#OVERFLOW_SINGLE_QUOTE}
     *         will be returned if a single quote didn't get closed, {@link SqlSplitter#OVERFLOW_DOUBLE_QUOTE} likewise
     *         for not closed double quotes.
     */
    public static int containsSqlEnd( String line, String delimiter, final int overflowValue )
    {
        int ret = overflowValue >= 0 ? NO_END : overflowValue;

        // / * * / comments
        boolean isComment = ( overflowValue == OVERFLOW_COMMENT );

        String quoteChar = null;
        if ( overflowValue == OVERFLOW_SINGLE_QUOTE )
        {
            quoteChar = "'";
        }
        else if ( overflowValue == OVERFLOW_DOUBLE_QUOTE )
        {
            quoteChar = "\"";
        }

        boolean isAlphaDelimiter = StringUtils.isAlpha( delimiter );

        if ( line == null || line.length() == 0 )
        {
            return ret;
        }

        int pos = 0;
        int maxpos = line.length() - 1;

        char c1;
        char c2 = line.charAt( 0 );
        statement: do
        {
            if ( isComment )
            {
                do
                {
                    // keep c2 in line
                    if ( pos < maxpos )
                    {
                        c2 = line.charAt( pos + 1 );
                    }

                    if ( startsWith( line, '*', pos ) && startsWith( line, '/', pos + 1 ) )
                    {
                        ret = NO_END;
                        isComment = false;

                        continue statement;
                    }
                }
                while ( pos++ < maxpos );

                // reached EOL
                break statement;
            }

            // if in quote-mode, search for end quote, respecting escaped characters
            if ( quoteChar != null )
            {
                String doubleQuote = quoteChar + quoteChar;
                do
                {
                    // keep c2 in line
                    if ( pos < maxpos )
                    {
                        c2 = line.charAt( pos + 1 );
                    }

                    if ( startsWith( line, "\\", pos ) || startsWith( line, doubleQuote, pos ) )
                    {
                        // skip next character, but stay in quote-mode
                        pos++;
                    }
                    else if ( startsWith( line, quoteChar, pos ) )
                    {
                        ret = NO_END;
                        quoteChar = null;

                        continue statement;
                    }
                }
                while ( pos++ < maxpos );

                // reach EOL
                break statement;
            }

            // use the nextchar from the previous iteration
            c1 = c2;
            if ( pos < maxpos )
            {
                // and set the following char
                c2 = line.charAt( pos + 1 );
            }
            else
            {
                // or reset to blank if the line has ended
                c2 = ' ';
            }

            // verify if current char indicates start of new quoted block
            if ( c1 == '\'' || c1 == '"' )
            {
                quoteChar = String.valueOf( c1 );
                ret = quoteChar.equals( "'" ) ? OVERFLOW_SINGLE_QUOTE : OVERFLOW_DOUBLE_QUOTE;
                continue statement;
            }

            // parse for a / * start of comment
            if ( c1 == '/' && c2 == '*' )
            {
                isComment = true;
                pos++;
                ret = OVERFLOW_COMMENT;
                continue statement;
            }

            if ( c1 == '-' && c2 == '-' )
            {
                return ret;
            }

            if ( startsWith( line, delimiter, pos ) )
            {
                if ( isAlphaDelimiter )
                {
                    // check if delimiter is at start or end of line, surrounded
                    // by non-alpha character
                    if ( ( pos == 0 || !isAlpha( line.charAt( pos - 1 ) ) )
                        && ( line.length() == pos + delimiter.length()
                            || !isAlpha( line.charAt( pos + delimiter.length() ) ) ) )
                    {
                        return pos + delimiter.length();
                    }
                }
                else
                {
                    return pos + delimiter.length();
                }
            }

        }
        while ( maxpos > pos++ );

        return ret;
    }

    /**
     * Small performance optimized replacement for {@link String#startsWith(String, int)}.
     * 
     * @param toParse the String to parse
     * @param delimiter the delimiter to look for
     * @param position the initial position to start the scan with
     */
    private static boolean startsWith( String toParse, String delimiter, int position )
    {
        if ( delimiter.length() == 1 )
        {
            return toParse.length() > position && toParse.charAt( position ) == delimiter.charAt( 0 );
        }
        else
        {
            return toParse.startsWith( delimiter, position );
        }
    }

    /**
     * @param toParse the String to parse
     * @param delimiter the delimiter to look for
     * @param position the initial position to start the scan with
     * @return
     */
    private static boolean startsWith( String toParse, char delimiter, int position )
    {
        return toParse.length() > position && toParse.charAt( position ) == delimiter;
    }

    /**
     * @param c the char to check
     * @return true if the given character is either a lower or an upperchase alphanumerical character
     */
    private static boolean isAlpha( char c )
    {
        return Character.isUpperCase( c ) || Character.isLowerCase( c );
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy