package org.agnitas.emm.extension.sqlparser; import java.io.IOException; import java.io.Reader; import java.util.List; import java.util.Vector; /** * A simple SQL parser. This parser is only used to split a SQL script * containing multiple SQL statements into a list of single statements * removing all comments. * * Technically, this class uses the <i>State Pattern</i>. (see http://en.wikipedia.org/wiki/State_pattern) * * @author md * * @see http://en.wikipedia.org/wiki/State_pattern */ public class SimpleSqlParser { /** State, when the parser is in the "command" level of the statement. (Not in string or comments) */ private final CommandState commandState; /** State indicating that the parser is currently reading characters inside a double-quoted string. */ private final QuotedStringState doubleQuotedStringState; /** State indicating that the parser is currently reading characters inside a single-quoted string. */ private final QuotedStringState singleQuotedStringState; /** State indicating that the parser is currently reading characters inside a backtick-quoted string. */ private final QuotedStringState backtickQuotedStringState; /** State indicating that the parser is possibly reading a single-line comment. (The first "-" was read). */ private final PossibleSingleLineCommentState possibleSingleLineCommentState; /** State indicating that the parser is currently reading a single-line comment. (The both "-" were read). */ private final SingleLineCommentState singleLineCommentState; /** State indicating that the parser is possibly reading a multi-line comment. (The "/" was read). */ private final PossibleMultiLineCommentState possibleMultiLineCommentState; /** State indicating that the parser is currently reading a multi-line comment. ("/*" was read). */ private final MultiLineCommentState multiLineCommentState; /** State indicating that the parser is possibly leaving the multi-line comment ("*" was read). */ private final PossibleMultiLineCommentEndState possibleMultiLineCommentEndState; /** List of previously parsed statements. */ private final List<String> statements; /** Reader to read the script character by character. */ private final Reader reader; /** The current state of the parser. */ private ParserState currentState; /** * Creates a new parser and binds the given Reader as source. * * @param reader Reader yielding the SQL script. */ public SimpleSqlParser( Reader reader) { this.reader = reader; this.statements = new Vector<String>(); // Create the internal states this.commandState = new CommandState( statements); this.backtickQuotedStringState = new QuotedStringState( '`'); this.singleQuotedStringState = new QuotedStringState( '\''); this.doubleQuotedStringState = new QuotedStringState( '"'); this.possibleSingleLineCommentState = new PossibleSingleLineCommentState(); this.singleLineCommentState = new SingleLineCommentState(); this.possibleMultiLineCommentState = new PossibleMultiLineCommentState(); this.multiLineCommentState = new MultiLineCommentState(); this.possibleMultiLineCommentEndState = new PossibleMultiLineCommentEndState(); // Define the transitions between the states setStateTransitions(); // Begin in CommandState this.currentState = commandState; } /** * Setup the transitions between the states. */ private void setStateTransitions() { this.commandState.setReachableStates( singleQuotedStringState, doubleQuotedStringState, backtickQuotedStringState, possibleSingleLineCommentState, possibleMultiLineCommentState); this.singleQuotedStringState.setReachableStates( commandState); this.doubleQuotedStringState.setReachableStates( commandState); this.backtickQuotedStringState.setReachableStates( commandState); this.possibleSingleLineCommentState.setReachableStates( commandState, this.singleLineCommentState); this.singleLineCommentState.setReachableStates( commandState); this.possibleMultiLineCommentState.setReachableStates( commandState, multiLineCommentState); this.multiLineCommentState.setReachableStates( possibleMultiLineCommentEndState); this.possibleMultiLineCommentEndState.setReachableStates( multiLineCommentState, commandState); } /** * Parses the SQL script reading character by character for the bound Reader. * * @return List of parsed SQL statements * * @throws IOException on errors reading from the bound Reader */ public List<String> parse() throws IOException { StatementBuffer buffer = new StatementBuffer(); while( currentState != null) { currentState = currentState.processChar(reader.read(), buffer); } this.statements.add( buffer.toString()); return statements; } }