#
#--
# Ronin SQL - A Ronin library providing support for SQL related security
# tasks.
#
# Copyright (c) 2007-2009 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#++
#
require 'ronin/code/sql/expr'
require 'ronin/code/sql/token'
require 'ronin/code/sql/field'
require 'ronin/code/sql/binary_expr'
require 'ronin/code/sql/unary_expr'
require 'ronin/code/sql/like'
require 'ronin/code/sql/in'
require 'ronin/extensions/meta'
module Ronin
module Code
module SQL
class Statement < Expr
attr_reader :clauses
#
# Creates a new Statement object connected to the specified
# _dialect_. If a _block_ is given, it will be evaluated within
# the newly created Statement object.
#
def initialize(dialect,options={},&block)
super()
@dialect = dialect
@clauses = []
options.each do |name,args|
if self.class.has_clause?(name)
clause(name,*args)
end
end
instance_eval(&block) if block
end
#
# Returns the Array denoting the precedence of clauses provided by
# the statement.
#
def self.clause_order
@@clause_order ||= []
end
#
# Returns the Hash of the clause names and the Clause classes
# provided by the statement.
#
def self.clauses
@@clauses ||= {}
end
#
# Returns +true+ if the statement provides a clause with the
# specified _name_, returns +false+ otherwise.
#
def self.has_clause?(name)
self.clauses.has_key?(name.to_sym)
end
#
# Returns +true+ if the statement has a clause with the specified
# _name_, returns +false+ otherwise.
#
def has_clause?(name)
index = self.class.clause_order.index(name.to_sym)
return !(@clauses[index].nil?)
end
#
# Returns the clause with the specified _name_.
#
def get_clause(name)
index = self.class.clause_order.index(name.to_sym)
return @clauses[index]
end
#
# Returns an Array of unformatted tokens that represent the
# statement.
#
def emit
tokens = []
@clauses.each do |clause|
if clause
tokens += clause.emit
end
end
return tokens
end
protected
#
# Adds a clause with the specified _name_, _clause_type_ and given
# _options_ to the statement.
#
# _options_ may contain the following:
# :before:: The name of the clause to take precedence
# over.
# :after:: The name of the clause which will take
# precedence over the newly added clause.
#
def self.clause(name,clause_type,options={})
name = name.to_sym
index = self.clause_order.length
if options[:before]
index = self.clause_order.index(options[:before])
elsif options[:after]
index = self.clause_order.index(options[:after]) + 1
end
self.clause_order.insert(index,name)
self.clauses[name] = clause_type
if clause_type.kind_of?(Class)
class_def(name) { |*args| clause(name,*args) }
else
class_def(name) { clause(name) }
end
return clause_type
end
def clause(name,*arguments)
clause_index = self.class.clause_order.index(name)
unless (@clauses[clause_index] && arguments.empty?)
clause_type = self.class.clauses[name]
@clauses[clause_index] = clause_type.new(*arguments)
end
return @clauses[clause_index]
end
def select(options={},&block)
@dialect.statement(:select,options,&block)
end
def method_missing(name,*arguments,&block)
if @dialect.has_statement?(name)
return @dialect.statement(name,*arguments,&block)
elsif @dialect.class.public_method_defined?(name)
return @dialect.send(name,*arguments,&block)
elsif (arguments.empty? && block.nil?)
return @dialect.field(name)
end
end
end
end
end
end