1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
|
module DBI
module SQL
#
# The PreparedStatement class attempts to provide binding functionality
# for database systems that do not have this built-in. This package
# emulates the whole concept of a statement.
#
class PreparedStatement
attr_accessor :unbound
# Convenience method for consumers that just need the tokens
# method.
def self.tokens(sql)
self.new(nil, sql).tokens
end
#
# "prepare" a statement.
#
# +quoter+ is deprecated and will eventually disappear, it is kept
# currently for compatibility. It is safe to pass nil to this parameter.
#
# +sql+ is the statement itself.
#
def initialize(quoter, sql)
@quoter, @sql = quoter, sql
prepare
end
# Break the sql string into parts.
#
# This is NOT a full lexer for SQL. It just breaks up the SQL
# string enough so that question marks, double question marks and
# quoted strings are separated. This is used when binding
# arguments to "?" in the SQL string.
#
# C-style (/* */) and Ada-style (--) comments are handled.
# Note:: Nested C-style comments are NOT handled!
#
def tokens
@sql.scan(%r{
(
-- .* (?# matches "--" style comments to the end of line or string )
| - (?# matches single "-" )
|
/[*] .*? [*]/ (?# matches C-style comments )
| / (?# matches single slash )
|
' ( [^'\\] | '' | \\. )* ' (?# match strings surrounded by apostophes )
|
" ( [^"\\] | "" | \\. )* " (?# match strings surrounded by " )
|
\?\?? (?# match one or two question marks )
|
[^-/'"?]+ (?# match all characters except ' " ? - and / )
)}x).collect {|t| t.first}
end
# attempts to bind the arguments in +args+ to this statement.
# Will raise StandardError if there are any extents issues.
def bind(args)
if @arg_index < args.size
raise "Too many SQL parameters"
elsif @arg_index > args.size
raise "Not enough SQL parameters"
end
@unbound.each do |res_pos, arg_pos|
@result[res_pos] = args[arg_pos]
end
@result.join("")
end
private
# prepares the statement for binding. This is done by scanning the
# statement and itemizing what needs to be bound and what does not.
#
# This information will then be used by #bind to appropriately map
# parameters to the intended destinations.
def prepare
@result = []
@unbound = {}
pos = 0
@arg_index = 0
tokens.each { |part|
case part
when '?'
@result[pos] = nil
@unbound[pos] = @arg_index
pos += 1
@arg_index += 1
when '??'
if @result[pos-1] != nil
@result[pos-1] << "?"
else
@result[pos] = "?"
pos += 1
end
else
if @result[pos-1] != nil
@result[pos-1] << part
else
@result[pos] = part
pos += 1
end
end
}
end
end # PreparedStatement
end
end
|