# 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