# The SQLTree::Parser class is used to construct a syntax tree # using SQLTree::Node instances from a tree of tokens. # # This class does only kickstart the parsing process and manages the # token stream that is being parsed. The actual parsing of the nodes # occurs in the parse class method of the different node classes. class SQLTree::Parser # The SQLTree::Parser::UnexpectedToken exception is thrown # when the parser meets a token that it not expect. # # This exceptions usually means that an SQL syntax error has been found, # however it can also mean that the SQL construct that is being used is # not (yet) supported by this library. Please create an issue on Github # if the latter is the case. class UnexpectedToken < StandardError attr_reader :expected_token, :actual_token def initialize(actual_token, expected_token = nil) # :nodoc: @expected_token, @actual_token = expected_token, actual_token message = "Unexpected token: found #{actual_token.inspect}" message << ", but expected #{expected_token.inspect}" if expected_token message << '!' super(message) end end # Kickstarts the parser by creating a new instance with the provided # string, and calling the parse! method on this instance. # # Do not use this method directly, but use the SQLTree.[] # method instead to parse SQL strings. # # sql_string:: The string to parse # options:: Options to pass to the parser def self.parse(sql_string, options = {}) self.new(sql_string, options).parse! end # Hash for parser options. attr_reader :options # Initializes the parser instance. # tokens:: The stream of tokens to turn into a syntax tree. If a # string is given, it is tokenized automatically. # options:: An optional hash of parser options. def initialize(tokens, options = {}) @tokens = tokens.kind_of?(String) ? SQLTree::Tokenizer.tokenize(tokens) : tokens @options = options end # Returns the current token that is being parsed. def current @current_token end # Returns the next token on the token queue, and moves the token queue # one position forward. This will update the result of the # SQLTree::Parser#current method. def next @current_token = @tokens.shift end # Consumes the current token(s), which will make the parser continue to the # next token (see SQLTree::Parser#next). # # This method will also check if the consumed token is of the expected type. # It will raise a SQLTree::Parser::UnexpectedToken exception if the # consumed token is not of the expected type # # *checks:: a list of token types to consume. def consume(*checks) # :raises: SQLTree::Parser::UnexpectedToken checks.each do |check| raise UnexpectedToken.new(self.current, check) unless check === self.next end end # Looks at the next token on the token queue without consuming it. # # The token queue will not be adjusted, will is the case when using # SQLTree::Parser#next. # # lookahead:: the number of positions to look ahead. Defaults to 1. def peek(lookahead = 1) @tokens[lookahead - 1] end # Peeks multiple tokens at the same time. # # This method will return an array of the requested number of tokens, # except for when the token stream is nearing its end. In this case, the # number of tokens returned can be less than requested. # lookahead_amount:: the amount of tokens to return from the # front of the token queue. def peek_multiple(lookahead_amount) @tokens[0, lookahead_amount] end # Prints the current list of tokens to $stdout for inspection. def debug puts @tokens.inspect end # Parser a complete SQL query into a tree of SQLTree::Node instances. # # Currently, SELECT, INSERT, UPDATE and DELETE queries are supported for # the most part. This emthod should not be called directly, but is called # by the SQLTree.[] method, e.g.: # # tree = SQLTree['SELECT * FROM table WHERE 1=1'] # def parse! case self.peek when SQLTree::Token::SELECT; SQLTree::Node::SelectQuery.parse(self) when SQLTree::Token::INSERT; SQLTree::Node::InsertQuery.parse(self) when SQLTree::Token::DELETE; SQLTree::Node::DeleteQuery.parse(self) when SQLTree::Token::UPDATE; SQLTree::Node::UpdateQuery.parse(self) when SQLTree::Token::BEGIN; SQLTree::Node::BeginStatement.parse(self) when SQLTree::Token::COMMIT; SQLTree::Node::CommitStatement.parse(self) when SQLTree::Token::ROLLBACK; SQLTree::Node::RollbackStatement.parse(self) when SQLTree::Token::SET; SQLTree::Node::SetQuery.parse(self) else raise UnexpectedToken.new(self.peek) end end end