diff options
author | Aaron Patterson <aaron.patterson@gmail.com> | 2010-10-12 15:57:26 -0700 |
---|---|---|
committer | Aaron Patterson <aaron.patterson@gmail.com> | 2010-10-26 13:44:07 -0700 |
commit | cc468d3ec81d6f1298fca91c0549584b36dafcc6 (patch) | |
tree | 02c6e8949a86127d7ad9c08aef18be9c7a966063 /activerecord | |
parent | 6ceffb8178b6d419d4453e1e24d4138215c35217 (diff) | |
download | rails-cc468d3ec81d6f1298fca91c0549584b36dafcc6.tar.gz rails-cc468d3ec81d6f1298fca91c0549584b36dafcc6.tar.bz2 rails-cc468d3ec81d6f1298fca91c0549584b36dafcc6.zip |
exec returns an AR::Result
Diffstat (limited to 'activerecord')
8 files changed, 104 insertions, 8 deletions
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 053b796991..db7f9b7974 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -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. diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index cf67185b83..0e4f68e85c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -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 diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index bcbef526bd..a36a182a9e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -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: diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 69bf2c0dcd..75bf579764 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -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) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 4c75dff891..58f7b74198 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -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? diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb new file mode 100644 index 0000000000..8deff1478f --- /dev/null +++ b/activerecord/lib/active_record/result.rb @@ -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 diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index f4a856e42a..52630c5ad4 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -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 diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 2d3047c875..f6ef155d66 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -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 { |