exec returns an AR::Result

This commit is contained in:
Aaron Patterson
2010-10-12 15:57:26 -07:00
parent 6ceffb8178
commit cc468d3ec8
8 changed files with 104 additions and 8 deletions

View File

@@ -448,8 +448,8 @@ module ActiveRecord #:nodoc:
# # You can use the same string replacement techniques as you can with ActiveRecord#find
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
# > [#<Post:0x36bff9c @attributes={"first_name"=>"The Cheap Man Buys Twice"}>, ...]
def find_by_sql(sql)
connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }
def find_by_sql(sql, bind_values = [])
connection.select_all(sanitize_sql(sql), "#{name} Load", bind_values).collect! { |record| instantiate(record) }
end
# Creates an object (or multiple objects) and saves it to the database, if validations pass.

View File

@@ -3,8 +3,8 @@ module ActiveRecord
module DatabaseStatements
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select_all(sql, name = nil)
select(sql, name)
def select_all(sql, name = nil, bind_values = [])
select(sql, name, bind_values)
end
# Returns a record hash with the column names as keys and column values
@@ -260,7 +260,7 @@ module ActiveRecord
protected
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select(sql, name = nil)
def select(sql, name = nil, bind_values = [])
end
undef_method :select

View File

@@ -12,6 +12,7 @@ require 'active_record/connection_adapters/abstract/connection_pool'
require 'active_record/connection_adapters/abstract/connection_specification'
require 'active_record/connection_adapters/abstract/query_cache'
require 'active_record/connection_adapters/abstract/database_limits'
require 'active_record/result'
module ActiveRecord
module ConnectionAdapters # :nodoc:

View File

@@ -50,6 +50,7 @@ module ActiveRecord
def initialize(connection, logger, config)
super(connection, logger)
@statements = {}
@config = config
end
@@ -131,6 +132,27 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
def exec(sql, name = nil, bind_values = [])
log(sql, name) do
# Don't cache statements without bind values
if bind_values.empty?
stmt = @connection.prepare(sql)
cols = stmt.columns
else
cache = @statements[sql] ||= {
:stmt => @connection.prepare(sql)
}
stmt = cache[:stmt]
cols = cache[:cols] ||= stmt.columns
stmt.reset!
stmt.bind_params bind_values.map { |col, val| val }
end
ActiveRecord::Result.new(cols, stmt.to_a)
end
end
def execute(sql, name = nil) #:nodoc:
log(sql, name) { @connection.execute(sql) }
end
@@ -280,8 +302,8 @@ module ActiveRecord
end
protected
def select(sql, name = nil) #:nodoc:
execute(sql, name).map do |row|
def select(sql, name = nil, bind_values = []) #:nodoc:
exec(sql, name, bind_values).map do |row|
record = {}
row.each do |key, value|
record[key.sub(/^"?\w+"?\./, '')] = value if key.is_a?(String)

View File

@@ -61,7 +61,7 @@ module ActiveRecord
def to_a
return @records if loaded?
@records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql)
@records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql, @bind_values)
preload = @preload_values
preload += @includes_values unless eager_loading?

View File

@@ -0,0 +1,30 @@
module ActiveRecord
###
# This class encapsulates a Result returned from calling +exec+ on any
# database connection adapter. For example:
#
# x = ActiveRecord::Base.connection.exec('SELECT * FROM foo')
# x # => #<ActiveRecord::Result:0xdeadbeef>
class Result
include Enumerable
attr_reader :columns, :rows
def initialize(columns, rows)
@columns = columns
@rows = rows
@hash_rows = nil
end
def each
hash_rows.each { |row| yield row }
end
private
def hash_rows
@hash_rows ||= @rows.map { |row|
ActiveSupport::OrderedHash[@columns.zip(row)]
}
end
end
end

View File

@@ -60,6 +60,41 @@ module ActiveRecord
bind_param = conn.substitute_for('foo', [])
assert_equal Arel.sql('?'), bind_param
end
def test_exec_no_binds
conn = Base.sqlite3_connection :database => ':memory:',
:adapter => 'sqlite3',
:timeout => 100
conn.exec('create table ex(id int, data string)')
result = conn.exec('SELECT id, data FROM ex')
assert_equal 0, result.rows.length
assert_equal 2, result.columns.length
assert_equal %w{ id data }, result.columns
conn.exec('INSERT INTO ex (id, data) VALUES (1, "foo")')
result = conn.exec('SELECT id, data FROM ex')
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
assert_equal [[1, 'foo']], result.rows
end
def test_exec_with_binds
conn = Base.sqlite3_connection :database => ':memory:',
:adapter => 'sqlite3',
:timeout => 100
conn.exec('create table ex(id int, data string)')
conn.exec('INSERT INTO ex (id, data) VALUES (1, "foo")')
result = conn.exec(
'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]])
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
assert_equal [[1, 'foo']], result.rows
end
end
end
end

View File

@@ -55,6 +55,14 @@ ActiveRecord::Base.connection.class.class_eval do
end
alias_method_chain :execute, :query_record
def exec_with_query_record(sql, name = nil, binds = [], &block)
$queries_executed ||= []
$queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
exec_without_query_record(sql, name, binds, &block)
end
alias_method_chain :exec, :query_record
end
ActiveRecord::Base.connection.class.class_eval {