module SQLTree::Node
# Abstract base class for all SQL expressions.
#
# To parse a string as an SQL expression, use:
#
# SQLTree::Node::Expression["(3 + 2 = 10 / 2) AND MD5('$ecret') = password"]
#
# This is an abtract class: its parse method will never return an
# SQLTree::Node::Expression instance, but always an instance
# of one of its subclasses. The concrete expression classes are defined in the
# SQLTree::Node::Expression namespace.
class Expression < Base
# Parses an SQL expression from a stream of tokens.
#
# This method will start trying to parse the token stream as a
# SQLTree::Node::Expression::BinaryOperator, which will in turn try
# to parse it as other kinds of expressions if a binary expression is not appropriate.
#
# tokens:: The token stream to parse from, which is an instance
# of SQLTree::Parser.
def self.parse(tokens)
SQLTree::Node::Expression::BinaryOperator.parse(tokens)
end
# Parses a single, atomic SQL expression. This can be either:
# * a full expression (or set of expressions) within parentheses.
# * a logical NOT expression
# * an SQL variable
# * an SQL function
# * a literal SQL value (numeric or string)
#
# tokens:: The token stream to parse from, which is an instance
# of SQLTree::Parser.
def self.parse_atomic(tokens)
if SQLTree::Token::LPAREN === tokens.peek
tokens.consume(SQLTree::Token::LPAREN)
expr = self.parse(tokens)
tokens.consume(SQLTree::Token::RPAREN)
expr
elsif tokens.peek.prefix_operator?
PrefixOperator.parse(tokens)
elsif tokens.peek.variable?
if SQLTree::Token::LPAREN === tokens.peek(2)
FunctionCall.parse(tokens)
elsif SQLTree::Token::DOT === tokens.peek(2)
Field.parse(tokens)
else
Variable.parse(tokens)
end
elsif SQLTree::Token::STRING_ESCAPE == tokens.peek
tokens.consume(SQLTree::Token::STRING_ESCAPE)
Value.parse(tokens)
elsif SQLTree::Token::INTERVAL === tokens.peek
IntervalValue.parse(tokens)
else
Value.parse(tokens)
end
end
# A prefix operator expression parses a construct that consists of an
# operator and an expression. Currently, the only prefix operator that
# is supported is the NOT keyword.
#
# This node has two child nodes: operator and rhs.
class PrefixOperator < SQLTree::Node::Expression
# The list of operator tokens that can be used as prefix operator.
TOKENS = [SQLTree::Token::NOT]
# The SQL operator as String that was used for this expression.
leaf :operator
# The right hand side of the prefix expression, i.e. the SQLTree::Node::Expression
# instance that appeared after the operator.
child :rhs
# Generates an SQL fragment for this prefix operator expression.
def to_sql(options = {})
"#{operator} #{rhs.to_sql(options)}"
end
# Parses the operator from the token stream.
# tokens:: the token stream to parse from.
def self.parse_operator(tokens)
tokens.next.literal.upcase
end
# Parses a prefix operator expression, by first parsing the operator
# and then parsing the right hand side expression.
# tokens:: the token stream to parse from, which is an instance
# of SQLTree::Parser.
def self.parse(tokens)
if tokens.peek.prefix_operator?
node = self.new
node.operator = parse_operator(tokens)
node.rhs = SQLTree::Node::Expression.parse(tokens)
return node
else
raise UnexpectedTokenException.new(tokens.peek)
end
end
end
# A postfix operator expression is a construct in which the operator appears
# after a (left-hand side) expression.
#
# This operator has two child nodes: operator and lhs.
#
# Currently, SQLTreedoes not support any postfix operator.
class PostfixOperator < SQLTree::Node::Expression
# The left-hand side SQLTree::Node::Expression instance that was parsed
# before the postfix operator.
child :lhs
# The postfoix operator for this expression as String.
leaf :operator
# Generates an SQL fragment for this postfix operator expression.
def to_sql(options = {})
"#{lhs.to_sql(options)} #{operator}"
end
# Parses a postfix operator expression. This method is not yet implemented.
# tokens:: The token stream to parse from, which is an instance
# of SQLTree::Parser.
def self.parse(tokens)
raise "Not yet implemented"
end
end
# A binary operator expression consists of a left-hand side expression (lhs), the
# binary operator itself and a right-hand side expression (rhs). It therefore has
# three children: operator, lhs and rhs.
#
# When multiple binary operators appear in an expression, they can be grouped
# using parenthesis (e.g. "(1 + 3) / 2", or "1 + (3 / 2)" ). If the parentheses
# are absent, the grouping is determined using the precedence of the operator.
class BinaryOperator < SQLTree::Node::Expression
# The token precedence list. Tokens that occur first in this list have
# the lowest precedence, the last tokens have the highest. This impacts
# parsing when no parentheses are used to indicate how operators should
# be grouped.
#
# The token precedence list is taken from the SQLite3 documentation:
# http://www.sqlite.org/lang_expr.html
TOKEN_PRECEDENCE = [
[SQLTree::Token::OR],
[SQLTree::Token::AND],
[SQLTree::Token::EQ, SQLTree::Token::NE, SQLTree::Token::IN, SQLTree::Token::LIKE, SQLTree::Token::ILIKE, SQLTree::Token::IS],
[SQLTree::Token::LT, SQLTree::Token::LTE, SQLTree::Token::GT, SQLTree::Token::GTE],
[SQLTree::Token::LSHIFT, SQLTree::Token::RSHIFT, SQLTree::Token::BINARY_AND, SQLTree::Token::BINARY_OR],
[SQLTree::Token::PLUS, SQLTree::Token::MINUS],
[SQLTree::Token::MULTIPLY, SQLTree::Token::DIVIDE, SQLTree::Token::MODULO],
[SQLTree::Token::CONCAT],
]
# A list of binary operator tokens, taken from the operator precedence list.
TOKENS = TOKEN_PRECEDENCE.flatten
# The operator to use for this binary operator expression.
leaf :operator
# The left hand side SQLTree::Node::Expression instance for this operator.
child :lhs
# The rights hand side SQLTree::Node::Expression instance for this operator.
child :rhs
# Generates an SQL fragment for this exression.
def to_sql(options = {})
"(#{lhs.to_sql(options)} #{operator} #{rhs.to_sql(options)})"
end
# Parses the operator for this expression.
#
# Some operators can be negated using the NOT operator (e.g. IS NOT,
# NOT LIKE). This is handled in this function as well.
#
# tokens:: The token stream to parse from.
def self.parse_operator(tokens)
if tokens.peek.optional_not_suffix? && tokens.peek(2).not?
return "#{tokens.next.literal.upcase} #{tokens.next.literal.upcase}"
elsif tokens.peek.not? && tokens.peek(2).optional_not_prefix?
return "#{tokens.next.literal.upcase} #{tokens.next.literal.upcase}"
else
return tokens.next.literal.upcase
end
end
# Parses the right hand side expression of the operator.
#
# Usually, this will parse another BinaryOperator expression with a higher
# precedence, but for some operators (+IN+ and +IS+), the default behavior
# is overriden to implement exceptions.
#
# tokens:: The token stream to parse from, which is an instance
# of SQLTree::Parser.
# precedence:: The current precedence level. By default, this method
# will try to parse a BinaryOperator expression with a
# one higher precedence level than the current level.
# operator:: The operator that was parsed.
def self.parse_rhs(tokens, precedence, operator = nil)
if ['IN', 'NOT IN'].include?(operator)
return List.parse(tokens)
elsif ['IS', 'IS NOT'].include?(operator)
tokens.consume(SQLTree::Token::NULL)
return SQLTree::Node::Expression::Value.new(nil)
else
return parse(tokens, precedence + 1)
end
end
# Parses the binary operator by first parsing the left hand side, then the operator
# itself, and finally the right hand side.
#
# BinaryOperator -> Expression Expression
#
# This method will try to parse the lowest precedence operator first, and gradually
# try to parse operators with a higher precedence level. The left and right hand side
# will both be parsed with a higher precedence level. This ensures that the resulting
# expression is grouped correctly.
#
# If no binary operator is found of any precedence level, this method will back on
# pasring an atomic expression, see {SQLTree::Node::Expression.parse_atomic}.
#
# @param [SQLTree::Parser] tokens The token stream to parse from.
# @param [Integer] precedence The current precedence level. Starts with the lowest
# precedence level (0) by default.
# @return [SQLTree::Node::Expression] The parsed expression. This may not be
# a binary operator expression, as this method falls back on parsing other
# expresison types if no binary operator is found.
# @raise [SQLTree::Parser::UnexpectedToken] if an unexpected token is
# encountered during parsing.
def self.parse(tokens, precedence = 0)
if precedence >= TOKEN_PRECEDENCE.length
return SQLTree::Node::Expression.parse_atomic(tokens)
else
expr = parse(tokens, precedence + 1)
while TOKEN_PRECEDENCE[precedence].include?(tokens.peek.class) || (tokens.peek && tokens.peek.not?)
operator = parse_operator(tokens)
rhs = parse_rhs(tokens, precedence, operator)
expr = self.new(:operator => operator, :lhs => expr, :rhs => rhs)
end
return expr
end
end
end
# Parses a comma-separated list of expressions, which is used after the IN operator.
# The attribute items contains the array of child nodes, all instances of
# {SQLTree::Node::Expression}.
class List < SQLTree::Node::Expression
# Include the enumerable module to simplify handling the items in this list.
include Enumerable
# The items that appear in the list, i.e. an array of {SQLTree::Node::Expression}
# instances.
child :items
def initialize(*items)
if items.length == 1 && items.first.kind_of?(Array)
@items = items.first
elsif items.length == 1 && items.first.kind_of?(Hash)
super(items.first)
else
@items
end
end
# Generates an SQL fragment for this list.
def to_sql(options = {})
"(#{items.map {|i| i.to_sql(options)}.join(', ')})"
end
# Returns true if this list has no items.
def empty?
items.empty?
end
# Makes sure the enumerable module works over the items in the list.
def each(&block) # :nodoc:
items.each(&block)
end
# Parses a list of expresison by parsing expressions as long as it sees
# a comma that indicates the presence of a next expression.
#
# List -> LPAREN (Expression (COMMA Expression)*)? RPAREN
#
# @param [SQLTree::Parser] tokens The token stream to parse from.
# @return [SQLTree::Node::Expression::List] The parsed list instance.
# @raise [SQLTree::Parser::UnexpectedToken] if an unexpected token is
# encountered during parsing.
def self.parse(tokens)
tokens.consume(SQLTree::Token::LPAREN)
items = []
unless SQLTree::Token::RPAREN === tokens.peek
items = self.parse_list(tokens, SQLTree::Node::Expression)
end
tokens.consume(SQLTree::Token::RPAREN)
self.new(items)
end
end
# Represents a SQL function call expression. This node has two child nodes:
# function and argument_list.
class FunctionCall < SQLTree::Node::Expression
# The name of the function that is called as String.
leaf :function
# The argument list as {SQLTree::Node::Expression::List} instance.
child :arguments
# Generates an SQL fragment for this function call.
def to_sql(options = {})
"#{function}(" + arguments.map { |e| e.to_sql(options) }.join(', ') + ")"
end
# Parses an SQL function call.
#
# FunctionCall -> List
#
# @param [SQLTree::Parser] tokens The token stream to parse from.
# @return [SQLTree::Node::Expression::FunctionCall] The parsed function call instance.
# @raise [SQLTree::Parser::UnexpectedToken] if an unexpected token is
# encountered during parsing.
def self.parse(tokens)
function_call = self.new(:function => tokens.next.literal, :arguments => [])
tokens.consume(SQLTree::Token::LPAREN)
function_call.arguments = self.parse_list(tokens) unless SQLTree::Token::RPAREN === tokens.peek
tokens.consume(SQLTree::Token::RPAREN)
return function_call
end
end
# Represents a postgresql INTERVAL value. Example: interval '2 days'.
#
# The value is the literal text of the interval (e.g. "2 days").
class IntervalValue < SQLTree::Node::Expression
# The actual value this node represents.
leaf :value
def initialize(value) # :nodoc:
@value = value
end
# Generates an SQL representation for this value.
def to_sql(options = {})
"interval " + quote_str(@value)
end
def self.parse(tokens)
tokens.consume(SQLTree::Token::INTERVAL)
if SQLTree::Token::String === tokens.peek
self.new(tokens.next.literal)
else
raise SQLTree::Parser::UnexpectedToken.new(tokens.current, :literal)
end
end
end
# Represents alitreal value in an SQL expression. This node is a leaf node
# and thus has no child nodes.
#
# A value can either be:
# * the SQL NULL keyword, which is represented by nil.
# * an SQL string, which is represented by a String instance.
# * an SQL date or time value, which can be represented as a Date,
# Time or DateTime instance.
# * an integer or decimal value, which is represented by an appropriate
# Numeric instance.
class Value < SQLTree::Node::Expression
# The actual value this node represents.
leaf :value
def initialize(value) # :nodoc:
@value = value
end
# Generates an SQL representation for this value.
#
# This method supports nil, string, numeric, date and time values.
#
# @return [String] A correctly quoted value that can be used safely
# within an SQL query
def to_sql(options = {})
case value
when nil; 'NULL'
when String; quote_str(@value)
when Numeric; @value.to_s
when Date; @value.strftime("'%Y-%m-%d'")
when DateTime, Time; @value.strftime("'%Y-%m-%d %H:%M:%S'")
else raise "Don't know how te represent this value in SQL!"
end
end
# Parses a literal value.
#
# Value -> (NULL | | )
#
# @param [SQLTree::Parser] tokens The token stream to parse from.
# @return [SQLTree::Node::Expression::Value] The parsed value instance.
# @raise [SQLTree::Parser::UnexpectedToken] if an unexpected token is
# encountered during parsing.
def self.parse(tokens)
case tokens.next
when SQLTree::Token::String, SQLTree::Token::Number
SQLTree::Node::Expression::Value.new(tokens.current.literal)
when SQLTree::Token::NULL
SQLTree::Node::Expression::Value.new(nil)
else
raise SQLTree::Parser::UnexpectedToken.new(tokens.current, :literal)
end
end
end
# Represents a variable within an SQL expression. This is a leaf node, so it
# does not have any child nodes. A variale can point to a field of a table or
# to another expression that was declared elsewhere.
class Variable < SQLTree::Node::Expression
# The name of the variable as String.
leaf :name
def initialize(name) # :nodoc:
@name = name
end
# Generates a quoted reference to the variable.
#
# @return [String] A correctly quoted variable that can be safely
# used in SQL queries
def to_sql(options = {})
quote_var(@name)
end
# Parses an SQL variable.
#
# Variable ->
#
# @param [SQLTree::Parser] tokens The token stream to parse from.
# @return [SQLTree::Node::Expression::Variable] The parsed variable instance.
# @raise [SQLTree::Parser::UnexpectedToken] if an unexpected token is
# encountered during parsing.
def self.parse(tokens)
if SQLTree::Token::Identifier === tokens.peek
self.new(tokens.next.literal)
else
raise SQLTree::Parser::UnexpectedToken.new(tokens.peek, :variable)
end
end
end
# Represents a reference to a field of a table in an SQL expression.
# This is a leaf node, which means that it does not have any child nodes.
class Field < Variable
# The table in which the field resides. This can be +nil+, in which case
# the table the field belongs to is inferred from the rest of the query.
leaf :table
# The name of the field.
leaf :name
alias :field :name
alias :field= :name=
# Initializes a new Field
def initialize(name, table = nil)
@name = name
@table = table
end
# Generates a correctly quoted reference to the field, which can
# be incorporated safely into an SQL query.
def to_sql(options = {})
@table.nil? ? quote_var(@name) : quote_var(@table) + '.' + quote_var(@name)
end
# Parses a field, either with or without the table reference.
#
# Field -> ( DOT)?
#
# @param [SQLTree::Parser] tokens The token stream to parse from.
# @return [SQLTree::Node::Expression::Field] The parsed field instance.
# @raise [SQLTree::Parser::UnexpectedToken] if an unexpected token is
# encountered during parsing.
def self.parse(tokens)
if SQLTree::Token::Identifier === tokens.peek
field_or_table = tokens.next.literal
else
raise SQLTree::Parser::UnexpectedToken.new(tokens.next)
end
if SQLTree::Token::DOT === tokens.peek
tokens.consume(SQLTree::Token::DOT)
if SQLTree::Token::Identifier === tokens.peek
self.new(tokens.next.literal, field_or_table)
else
raise SQLTree::Parser::UnexpectedToken.new(tokens.next)
end
else
self.new(field_or_table)
end
end
end
end
end