From d7799fadf93647b7c2ae745881882efbc849982b Mon Sep 17 00:00:00 2001 From: Anil Wadghule Date: Tue, 19 Oct 2010 22:48:54 +0530 Subject: Fix SQLite adapter name [#5842 state:resolved] --- activerecord/test/cases/migration_test.rb | 8 ++++---- activerecord/test/cases/schema_dumper_test.rb | 4 ++-- activerecord/test/cases/transactions_test.rb | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index ef949300b0..e6eef805cf 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -422,7 +422,7 @@ if ActiveRecord::Base.connection.supports_migrations? # Sybase, and SQLite3 will not allow you to add a NOT NULL # column to a table without a default value. - unless current_adapter?(:SybaseAdapter, :SQLiteAdapter) + unless current_adapter?(:SybaseAdapter, :SQLite3Adapter) def test_add_column_not_null_without_default Person.connection.create_table :testings do |t| t.column :foo, :string @@ -821,7 +821,7 @@ if ActiveRecord::Base.connection.supports_migrations? end end - if current_adapter?(:SQLiteAdapter) + if current_adapter?(:SQLite3Adapter) def test_rename_table_for_sqlite_should_work_with_reserved_words begin assert_nothing_raised do @@ -1131,7 +1131,7 @@ if ActiveRecord::Base.connection.supports_migrations? # so this happens there too assert_kind_of BigDecimal, b.value_of_e assert_equal BigDecimal("2.7182818284590452353602875"), b.value_of_e - elsif current_adapter?(:SQLiteAdapter) + elsif current_adapter?(:SQLite3Adapter) # - SQLite3 stores a float, in violation of SQL assert_kind_of BigDecimal, b.value_of_e assert_in_delta BigDecimal("2.71828182845905"), b.value_of_e, 0.00000000000001 @@ -1588,7 +1588,7 @@ if ActiveRecord::Base.connection.supports_migrations? end end - if current_adapter?(:PostgreSQLAdapter) || current_adapter?(:SQLiteAdapter) || current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter) + if current_adapter?(:PostgreSQLAdapter) || current_adapter?(:SQLite3Adapter) || current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter) def test_xml_creates_xml_column type = current_adapter?(:PostgreSQLAdapter) ? 'xml' : :text diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 66446b6b7e..9b2c7c00df 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -100,7 +100,7 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{c_int_4.*}, output assert_no_match %r{c_int_4.*:limit}, output - elsif current_adapter?(:SQLiteAdapter) + elsif current_adapter?(:SQLite3Adapter) assert_match %r{c_int_1.*:limit => 1}, output assert_match %r{c_int_2.*:limit => 2}, output assert_match %r{c_int_3.*:limit => 3}, output @@ -109,7 +109,7 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{c_int_without_limit.*}, output assert_no_match %r{c_int_without_limit.*:limit}, output - if current_adapter?(:SQLiteAdapter) + if current_adapter?(:SQLite3Adapter) assert_match %r{c_int_5.*:limit => 5}, output assert_match %r{c_int_6.*:limit => 6}, output assert_match %r{c_int_7.*:limit => 7}, output diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 44af54b143..0fbcef4091 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -420,7 +420,7 @@ class TransactionTest < ActiveRecord::TestCase end def test_sqlite_add_column_in_transaction - return true unless current_adapter?(:SQLite3Adapter, :SQLiteAdapter) + return true unless current_adapter?(:SQLite3Adapter) # Test first if column creation/deletion works correctly when no # transaction is in place. -- cgit v1.2.3 From b1b26af9a2f1c2037f7c2167d747ed33cc639763 Mon Sep 17 00:00:00 2001 From: Tim Morgan Date: Thu, 14 Oct 2010 22:31:05 -0500 Subject: Allow default_scope to accept a Proc. --- activerecord/lib/active_record/base.rb | 7 ++++++- activerecord/test/cases/relation_scoping_test.rb | 11 +++++++++++ activerecord/test/models/post.rb | 6 ++++++ 3 files changed, 23 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 78b3507dd9..879f02ff6a 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1143,7 +1143,12 @@ MSG end def current_scoped_methods #:nodoc: - scoped_methods.last + method = scoped_methods.last + if method.respond_to?(:call) + unscoped(&method) + else + method + end end def reset_scoped_methods #:nodoc: diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index 64365c1d75..965bdacc1a 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -311,6 +311,17 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal expected, received end + def test_default_scope_with_lambda + expected = Post.find_all_by_author_id(2) + PostForAuthor.selected_author = 2 + received = PostForAuthor.all + assert_equal expected, received + expected = Post.find_all_by_author_id(1) + PostForAuthor.selected_author = 1 + received = PostForAuthor.all + assert_equal expected, received + end + def test_default_scope_is_unscoped_on_find assert_equal 1, DeveloperCalledDavid.count assert_equal 11, DeveloperCalledDavid.unscoped.count diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index a3cb9c724a..61e782ff14 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -113,3 +113,9 @@ class PostWithComment < ActiveRecord::Base self.table_name = 'posts' default_scope where("posts.comments_count > 0").order("posts.comments_count ASC") end + +class PostForAuthor < ActiveRecord::Base + self.table_name = 'posts' + cattr_accessor :selected_author + default_scope lambda { where(:author_id => PostForAuthor.selected_author) } +end -- cgit v1.2.3 From e68f339aae4d3bc1bcf46b65cb8dcddc0ad2a435 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 19 Oct 2010 15:07:44 -0700 Subject: default scope can accept any object that responds to #call --- activerecord/lib/active_record/base.rb | 2 +- activerecord/test/cases/relation_scoping_test.rb | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 879f02ff6a..6720f0687a 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1145,7 +1145,7 @@ MSG def current_scoped_methods #:nodoc: method = scoped_methods.last if method.respond_to?(:call) - unscoped(&method) + relation.scoping { method.call } else method end diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index 965bdacc1a..689cce8746 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -322,6 +322,24 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal expected, received end + def test_default_scope_with_thing_that_responds_to_call + klass = Class.new(ActiveRecord::Base) do + self.table_name = 'posts' + end + + klass.class_eval do + default_scope Class.new(Struct.new(:klass)) { + def call + klass.where(:author_id => 2) + end + }.new(self) + end + + records = klass.all + assert_equal 1, records.length + assert_equal 2, records.first.author_id + end + def test_default_scope_is_unscoped_on_find assert_equal 1, DeveloperCalledDavid.count assert_equal 11, DeveloperCalledDavid.unscoped.count -- cgit v1.2.3 From 56be4c897ae52d73583c51dec155e6161e3dc900 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 19 Oct 2010 17:12:45 -0700 Subject: avoid creating the proc object if possible --- activerecord/lib/active_record/named_scope.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 6ab84df25b..b767a2b4a8 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -97,11 +97,11 @@ module ActiveRecord # # Article.published.new.published # => true # Article.published.create.published # => true - def scope(name, scope_options = {}, &block) + def scope(name, scope_options = {}) name = name.to_sym valid_scope_name?(name) - extension = Module.new(&block) if block_given? + extension = Module.new(&Proc.new) if block_given? scopes[name] = lambda do |*args| options = scope_options.is_a?(Proc) ? scope_options.call(*args) : scope_options -- cgit v1.2.3 From d2898d4ef80dc74ef0d6204b5c7f50877659e50e Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 19 Oct 2010 17:27:50 -0700 Subject: scopes can take an object that responds to `call` --- activerecord/lib/active_record/named_scope.rb | 2 +- activerecord/test/cases/named_scope_test.rb | 6 ++++++ activerecord/test/models/topic.rb | 7 +++++++ 3 files changed, 14 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index b767a2b4a8..0b92ba5caa 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -104,7 +104,7 @@ module ActiveRecord extension = Module.new(&Proc.new) if block_given? scopes[name] = lambda do |*args| - options = scope_options.is_a?(Proc) ? scope_options.call(*args) : scope_options + options = scope_options.respond_to?(:call) ? scope_options.call(*args) : scope_options relation = if options.is_a?(Hash) scoped.apply_finder_options(options) diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index cc4438395e..fb24c65fff 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -125,6 +125,12 @@ class NamedScopeTest < ActiveRecord::TestCase assert_equal posts_with_authors_at_address_titles, Post.with_authors_at_address(address).find(:all, :select => 'title') end + def test_scope_with_object + objects = Topic.with_object + assert_operator objects.length, :>, 0 + assert objects.all?(&:approved?), 'all objects should be approved' + end + def test_extensions assert_equal 1, Topic.anonymous_extension.one assert_equal 2, Topic.named_extension.two diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index ba2fe1987b..82d4b5997f 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -18,6 +18,13 @@ class Topic < ActiveRecord::Base 1 end end + + scope :with_object, Class.new(Struct.new(:klass)) { + def call + klass.where(:approved => true) + end + }.new(self) + module NamedExtension def two 2 -- cgit v1.2.3 From 78b6f6410522fa634b639645e7f53bca31f5bccc Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 19 Oct 2010 17:37:55 -0700 Subject: avoid creating a proc object when possible --- activerecord/lib/active_record/relation/query_methods.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 59ce76ea42..e8bd4dc485 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -117,8 +117,8 @@ module ActiveRecord relation end - def extending(*modules, &block) - modules << Module.new(&block) if block_given? + def extending(*modules) + modules << Module.new(&Proc.new) if block_given? relation = clone relation.send(:apply_modules, modules.flatten) -- cgit v1.2.3 From 08636527b5f5ba1c96937e86fd0c6e816462a1eb Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 19 Oct 2010 17:43:30 -0700 Subject: avoid cloning if we do not need to clone --- activerecord/lib/active_record/relation/query_methods.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index e8bd4dc485..d0f11526ac 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -120,6 +120,8 @@ module ActiveRecord def extending(*modules) modules << Module.new(&Proc.new) if block_given? + return self if modules.empty? + relation = clone relation.send(:apply_modules, modules.flatten) relation -- cgit v1.2.3 From c56fea2be49cc4a61d385835a097772f4d27a5dd Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 19 Oct 2010 17:44:33 -0700 Subject: use shortened version to generate a sql literal --- activerecord/lib/active_record/relation/query_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index d0f11526ac..64d2cb0203 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -235,7 +235,7 @@ module ActiveRecord @implicit_readonly = false arel.project(*selects) else - arel.project(Arel::SqlLiteral.new(@klass.quoted_table_name + '.*')) + arel.project(Arel.sql(@klass.quoted_table_name + '.*')) end end -- cgit v1.2.3 From dc16163d068479993002f5540368b8dbcc3724d1 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 19 Oct 2010 17:46:54 -0700 Subject: use array math rather than looping through the array --- activerecord/lib/active_record/relation/spawn_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index dcddc3dac4..c2af4b1571 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -38,7 +38,7 @@ module ActiveRecord merged_relation.where_values = merged_wheres - Relation::SINGLE_VALUE_METHODS.reject {|m| m == :lock}.each do |method| + (Relation::SINGLE_VALUE_METHODS - [:lock]).each do |method| value = r.send(:"#{method}_value") merged_relation.send(:"#{method}_value=", value) unless value.nil? end -- cgit v1.2.3 From 8d5829f149e6817babc4616c28e53162afbe9c48 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 19 Oct 2010 17:51:46 -0700 Subject: dup rather than create so many arrays --- activerecord/lib/active_record/relation/spawn_methods.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index c2af4b1571..e644670019 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -26,14 +26,14 @@ module ActiveRecord merged_relation = merged_relation.joins(r.joins_values) - merged_wheres = @where_values + merged_wheres = @where_values.dup r.where_values.each do |w| if w.respond_to?(:operator) && w.operator == :== merged_wheres = merged_wheres.reject {|p| p.respond_to?(:operator) && p.operator == :== && p.operand1.name == w.operand1.name } end - merged_wheres += [w] + merged_wheres << w end merged_relation.where_values = merged_wheres -- cgit v1.2.3 From 8c511c0b3ce02df75e8c089cb6cfd4e0a958a0cf Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 19 Oct 2010 20:53:53 -0700 Subject: swap out some n^2 for some n --- .../lib/active_record/relation/spawn_methods.rb | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index e644670019..b8b0898cc8 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -26,15 +26,21 @@ module ActiveRecord merged_relation = merged_relation.joins(r.joins_values) - merged_wheres = @where_values.dup + merged_wheres = @where_values.dup + r.where_values - r.where_values.each do |w| - if w.respond_to?(:operator) && w.operator == :== - merged_wheres = merged_wheres.reject {|p| p.respond_to?(:operator) && p.operator == :== && p.operand1.name == w.operand1.name } - end + equality_wheres = merged_wheres.find_all { |w| + w.respond_to?(:operator) && w.operator == :== + } - merged_wheres << w - end + equality_wheres_by_operand = equality_wheres.group_by { |eq| + eq.left.name + } + + duplicates = equality_wheres_by_operand.map { |name, list| + list[0...-1] + }.flatten + + merged_wheres -= duplicates merged_relation.where_values = merged_wheres -- cgit v1.2.3 From dbc5d2694f0c77ca9de43306602969fdd3dbd20e Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 20 Oct 2010 08:41:18 -0700 Subject: reduce duplicate where removal to one loop --- .../lib/active_record/relation/spawn_methods.rb | 28 ++++++++++------------ 1 file changed, 13 insertions(+), 15 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index b8b0898cc8..648a02f1cc 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -26,21 +26,19 @@ module ActiveRecord merged_relation = merged_relation.joins(r.joins_values) - merged_wheres = @where_values.dup + r.where_values - - equality_wheres = merged_wheres.find_all { |w| - w.respond_to?(:operator) && w.operator == :== - } - - equality_wheres_by_operand = equality_wheres.group_by { |eq| - eq.left.name - } - - duplicates = equality_wheres_by_operand.map { |name, list| - list[0...-1] - }.flatten - - merged_wheres -= duplicates + merged_wheres = @where_values + r.where_values + + # Remove duplicates, last one wins. + seen = {} + merged_wheres = merged_wheres.reverse.reject { |w| + nuke = false + if w.respond_to?(:operator) && w.operator == :== + name = w.left.name + nuke = seen[name] + seen[name] = true + end + nuke + }.reverse merged_relation.where_values = merged_wheres -- cgit v1.2.3 From 21beedf1ff925613fb1ca9b3cf44d10526b64a2e Mon Sep 17 00:00:00 2001 From: Jan Date: Tue, 12 Oct 2010 12:42:09 +0800 Subject: default scope merge where clauses [#5488 state:resolved] --- activerecord/lib/active_record/base.rb | 1 + activerecord/test/cases/base_test.rb | 1 + activerecord/test/cases/relation_scoping_test.rb | 17 +++++++++++++++++ 3 files changed, 19 insertions(+) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 6720f0687a..0e41a1c35c 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1139,6 +1139,7 @@ MSG # Article.new.published # => true # Article.create.published # => true def default_scope(options = {}) + reset_scoped_methods self.default_scoping << construct_finder_arel(options, default_scoping.pop) end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 8acee9ac71..21bd61c096 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1456,6 +1456,7 @@ class BasicsTest < ActiveRecord::TestCase UnloadablePost.class_eval do default_scope order('posts.comments_count ASC') end + UnloadablePost.scoped_methods # make Thread.current[:UnloadablePost_scoped_methods] not nil UnloadablePost.unloadable assert_not_nil Thread.current[:UnloadablePost_scoped_methods] diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index 689cce8746..a27e2e72cd 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -393,6 +393,23 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal 100000, klass.first.salary end + def test_default_scope_called_twice_in_different_place_merges_where_clause + Developer.destroy_all + Developer.create!(:name => "David", :salary => 80000) + Developer.create!(:name => "David", :salary => 100000) + Developer.create!(:name => "Brian", :salary => 100000) + + klass = Class.new(Developer) + klass.class_eval do + default_scope where("name = 'David'") + default_scope where("salary = 100000") + end + + assert_equal 1, klass.count + assert_equal "David", klass.first.name + assert_equal 100000, klass.first.salary + end + def test_method_scope expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.salary } received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary } -- cgit v1.2.3 From 96e8e97e78ddcf68474c4c903895c0b33653a720 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 20 Oct 2010 09:38:15 -0700 Subject: removing unused variables --- activerecord/test/cases/associations/callbacks_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb index 15537d6940..6a30e2905b 100644 --- a/activerecord/test/cases/associations/callbacks_test.rb +++ b/activerecord/test/cases/associations/callbacks_test.rb @@ -72,7 +72,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase def test_has_many_callbacks_for_save_on_parent jack = Author.new :name => "Jack" - post = jack.posts_with_callbacks.build :title => "Call me back!", :body => "Before you wake up and after you sleep" + jack.posts_with_callbacks.build :title => "Call me back!", :body => "Before you wake up and after you sleep" callback_log = ["before_adding", "after_adding#{jack.posts_with_callbacks.first.id}"] assert_equal callback_log, jack.post_log @@ -149,7 +149,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase assert !@david.unchangable_posts.include?(@authorless) begin @david.unchangable_posts << @authorless - rescue Exception => e + rescue Exception end assert @david.post_log.empty? assert !@david.unchangable_posts.include?(@authorless) -- cgit v1.2.3 From 03d4b86e78b161474e4228e0f4a50ab18f4d21cb Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 20 Oct 2010 14:58:16 -0700 Subject: removing unused variable --- activerecord/lib/active_record/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 0e41a1c35c..1a9f7449b7 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -973,7 +973,7 @@ module ActiveRecord #:nodoc: if match.scope? self.class_eval <<-METHOD, __FILE__, __LINE__ + 1 def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args) - options = args.extract_options! # options = args.extract_options! + args.extract_options! # args.extract_options! attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments( [:#{attribute_names.join(',:')}], args # [:user_name, :password], args ) # ) -- cgit v1.2.3 From 954bd126eca03c666996520a2d996e32c772487e Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 20 Oct 2010 15:07:17 -0700 Subject: extract options is not necessary --- activerecord/lib/active_record/base.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 1a9f7449b7..dd50e1658a 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -973,7 +973,6 @@ module ActiveRecord #:nodoc: if match.scope? self.class_eval <<-METHOD, __FILE__, __LINE__ + 1 def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args) - args.extract_options! # args.extract_options! attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments( [:#{attribute_names.join(',:')}], args # [:user_name, :password], args ) # ) -- cgit v1.2.3 From fb835fc1e2d915deed2a1a7bd6d2e14d526897df Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 20 Oct 2010 15:46:04 -0700 Subject: use zip + Hash.[] rather than looping with an index --- activerecord/lib/active_record/base.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index dd50e1658a..315598fc4b 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -988,9 +988,7 @@ module ActiveRecord #:nodoc: end def construct_attributes_from_arguments(attribute_names, arguments) - attributes = {} - attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] } - attributes + Hash[attribute_names.zip(arguments)] end # Similar in purpose to +expand_hash_conditions_for_aggregates+. -- cgit v1.2.3 From 7f444a3db68c9538544f8e38c678a165a00642e3 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 20 Oct 2010 15:51:44 -0700 Subject: roll up weird method to meta programmed method --- activerecord/lib/active_record/base.rb | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 315598fc4b..630bf7fe85 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -972,13 +972,11 @@ module ActiveRecord #:nodoc: super unless all_attributes_exists?(attribute_names) if match.scope? self.class_eval <<-METHOD, __FILE__, __LINE__ + 1 - def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args) - attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments( - [:#{attribute_names.join(',:')}], args # [:user_name, :password], args - ) # ) - # - scoped(:conditions => attributes) # scoped(:conditions => attributes) - end # end + def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args) + attributes = Hash[[:#{attribute_names.join(',:')}].zip(args)] # attributes = Hash[[:user_name, :password].zip(args)] + # + scoped(:conditions => attributes) # scoped(:conditions => attributes) + end # end METHOD send(method_id, *arguments) end @@ -987,10 +985,6 @@ module ActiveRecord #:nodoc: end end - def construct_attributes_from_arguments(attribute_names, arguments) - Hash[attribute_names.zip(arguments)] - end - # Similar in purpose to +expand_hash_conditions_for_aggregates+. def expand_attribute_names_for_aggregates(attribute_names) expanded_attribute_names = [] -- cgit v1.2.3 From 784177aeeeea3265e74b2ec8c3a2929a160ba904 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 20 Oct 2010 16:17:15 -0700 Subject: only call `column_methods_hash` once, use array math for faster test of existence --- activerecord/lib/active_record/base.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 630bf7fe85..9c4bded1f3 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -991,19 +991,18 @@ module ActiveRecord #:nodoc: attribute_names.each do |attribute_name| unless (aggregation = reflect_on_aggregation(attribute_name.to_sym)).nil? aggregate_mapping(aggregation).each do |field_attr, aggregate_attr| - expanded_attribute_names << field_attr + expanded_attribute_names << field_attr.to_sym end else - expanded_attribute_names << attribute_name + expanded_attribute_names << attribute_name.to_sym end end expanded_attribute_names end def all_attributes_exists?(attribute_names) - expand_attribute_names_for_aggregates(attribute_names).all? { |name| - column_methods_hash.include?(name.to_sym) - } + (expand_attribute_names_for_aggregates(attribute_names) - + column_methods_hash.keys).empty? end protected -- cgit v1.2.3 From 4be0fc124a13704b309bb6d79da4fdfdbee67cf9 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 20 Oct 2010 16:20:49 -0700 Subject: use a map and flatten to avoid << calls on array --- activerecord/lib/active_record/base.rb | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 9c4bded1f3..053b796991 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -987,17 +987,15 @@ module ActiveRecord #:nodoc: # Similar in purpose to +expand_hash_conditions_for_aggregates+. def expand_attribute_names_for_aggregates(attribute_names) - expanded_attribute_names = [] - attribute_names.each do |attribute_name| + attribute_names.map { |attribute_name| unless (aggregation = reflect_on_aggregation(attribute_name.to_sym)).nil? - aggregate_mapping(aggregation).each do |field_attr, aggregate_attr| - expanded_attribute_names << field_attr.to_sym + aggregate_mapping(aggregation).map do |field_attr, _| + field_attr.to_sym end else - expanded_attribute_names << attribute_name.to_sym + attribute_name.to_sym end - end - expanded_attribute_names + }.flatten end def all_attributes_exists?(attribute_names) -- cgit v1.2.3 From 828bb94d5a41695d30cb46ccafb152c98cdeae0a Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 20 Oct 2010 16:41:50 -0700 Subject: use grep instead of select + is_a? --- activerecord/lib/active_record/reflection.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index db18fb7c0f..a2260e9a19 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -36,7 +36,7 @@ module ActiveRecord # Returns an array of AggregateReflection objects for all the aggregations in the class. def reflect_on_all_aggregations - reflections.values.select { |reflection| reflection.is_a?(AggregateReflection) } + reflections.values.grep(AggregateReflection) end # Returns the AggregateReflection object for the named +aggregation+ (use the symbol). @@ -58,7 +58,7 @@ module ActiveRecord # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations # def reflect_on_all_associations(macro = nil) - association_reflections = reflections.values.select { |reflection| reflection.is_a?(AssociationReflection) } + association_reflections = reflections.values.grep(AssociationReflection) macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections end -- cgit v1.2.3 From 410114e85ac0048de3fd932a5aaac2a11b45be86 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 20 Oct 2010 17:17:22 -0700 Subject: adding a test to ensure offsets with no limits will work [#5316 state:resolved] --- activerecord/test/cases/base_test.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'activerecord') diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 21bd61c096..e63e1fbe09 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1150,6 +1150,12 @@ class BasicsTest < ActiveRecord::TestCase assert_equal 3, scoped_developers.size end + def test_no_limit_offset + assert_nothing_raised do + Developer.find(:all, :offset => 2) + end + end + def test_scoped_find_limit_offset scoped_developers = Developer.send(:with_scope, :find => { :limit => 3, :offset => 2 }) do Developer.find(:all, :order => 'id') -- cgit v1.2.3 From 5b86c3e5bb2bb54003d8f211b46a7b992355dbf5 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Fri, 22 Oct 2010 10:28:53 +0200 Subject: has_one maintains the association with separate after_create/after_update This way parent models can get their own after_create and after_update callbacks fired after has_one has done its job. --- activerecord/CHANGELOG | 3 ++ .../lib/active_record/autosave_association.rb | 11 ++++++- .../test/cases/autosave_association_test.rb | 20 ++++++++++++ activerecord/test/models/eye.rb | 37 ++++++++++++++++++++++ activerecord/test/schema/schema.rb | 8 +++++ 5 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 activerecord/test/models/eye.rb (limited to 'activerecord') diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 75657cb6ee..d3530e0da9 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,8 @@ *Rails 3.1.0 (unreleased)* +* has_one maintains the association with separate after_create/after_update instead + of a single after_save. [fxn] + * The following code: Model.limit(10).scoping { Model.count } diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 21a9a1f2cb..4a2c078e91 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -167,7 +167,16 @@ module ActiveRecord else if reflection.macro == :has_one define_method(save_method) { save_has_one_association(reflection) } - after_save save_method + # Configures two callbacks instead of a single after_save so that + # the model may rely on their execution order relative to its + # own callbacks. + # + # For example, given that after_creates run before after_saves, if + # we configured instead an after_save there would be no way to fire + # a custom after_create callback after the child association gets + # created. + after_create save_method + after_update save_method else define_method(save_method) { save_belongs_to_association(reflection) } before_save save_method diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 52382f5afc..89be94c81f 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -17,6 +17,7 @@ require 'models/tag' require 'models/tagging' require 'models/treasure' require 'models/company' +require 'models/eye' class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase def test_autosave_should_be_a_valid_option_for_has_one @@ -170,6 +171,25 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas firm.account = Account.find(:first).clone assert_queries(2) { firm.save! } end + + def test_callbacks_firing_order_on_create + eye = Eye.create(:iris_attributes => {:color => 'honey'}) + assert_equal [true, false], eye.after_create_callbacks_stack + end + + def test_callbacks_firing_order_on_update + eye = Eye.create(:iris_attributes => {:color => 'honey'}) + eye.update_attributes(:iris_attributes => {:color => 'green'}) + assert_equal [true, false], eye.after_update_callbacks_stack + end + + def test_callbacks_firing_order_on_save + eye = Eye.create(:iris_attributes => {:color => 'honey'}) + assert_equal [false, false], eye.after_save_callbacks_stack + + eye.update_attributes(:iris_attributes => {:color => 'blue'}) + assert_equal [false, false, false, false], eye.after_save_callbacks_stack + end end class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase diff --git a/activerecord/test/models/eye.rb b/activerecord/test/models/eye.rb new file mode 100644 index 0000000000..77f17b578e --- /dev/null +++ b/activerecord/test/models/eye.rb @@ -0,0 +1,37 @@ +class Eye < ActiveRecord::Base + attr_reader :after_create_callbacks_stack + attr_reader :after_update_callbacks_stack + attr_reader :after_save_callbacks_stack + + # Callbacks configured before the ones has_one sets up. + after_create :trace_after_create + after_update :trace_after_update + after_save :trace_after_save + + has_one :iris + accepts_nested_attributes_for :iris + + # Callbacks configured after the ones has_one sets up. + after_create :trace_after_create2 + after_update :trace_after_update2 + after_save :trace_after_save2 + + def trace_after_create + (@after_create_callbacks_stack ||= []) << iris.new_record? + end + alias trace_after_create2 trace_after_create + + def trace_after_update + (@after_update_callbacks_stack ||= []) << iris.changed? + end + alias trace_after_update2 trace_after_update + + def trace_after_save + (@after_save_callbacks_stack ||= []) << iris.changed? + end + alias trace_after_save2 trace_after_save +end + +class Iris < ActiveRecord::Base + belongs_to :eye +end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index ea62833d81..fe59d8aeec 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -156,6 +156,11 @@ ActiveRecord::Schema.define do t.integer :company_id end + create_table :iris, :force => true do |t| + t.integer :eye + t.string :color + end + create_table :customers, :force => true do |t| t.string :name t.integer :balance, :default => 0 @@ -194,6 +199,9 @@ ActiveRecord::Schema.define do t.integer :car_id end + create_table :eyes, :force => true do |t| + end + create_table :tyres, :force => true do |t| t.integer :car_id end -- cgit v1.2.3 From a031fc57c85b78a51769ab304fdae9c9fac6c84e Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Fri, 22 Oct 2010 16:54:23 +0200 Subject: made a pass in AR's schema.rb to keep (most) create statements in lexicographic order, and fixed an FK --- activerecord/test/schema/schema.rb | 119 +++++++++++++++++++------------------ 1 file changed, 60 insertions(+), 59 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index fe59d8aeec..bb80a1ca17 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -18,8 +18,13 @@ ActiveRecord::Schema.define do end - # Please keep these create table statements in alphabetical order - # unless the ordering matters. In which case, define them below + # ------------------------------------------------------------------- # + # # + # Please keep these create table statements in alphabetical order # + # unless the ordering matters. In which case, define them below. # + # # + # ------------------------------------------------------------------- # + create_table :accounts, :force => true do |t| t.integer :firm_id t.string :firm_name @@ -54,7 +59,6 @@ ActiveRecord::Schema.define do t.column :favorite_author_id, :integer end - create_table :auto_id_tests, :force => true, :id => false do |t| t.primary_key :auto_id t.integer :value @@ -78,6 +82,11 @@ ActiveRecord::Schema.define do t.boolean :value end + create_table :bulbs, :force => true do |t| + t.integer :car_id + t.string :name + end + create_table "CamelCase", :force => true do |t| t.string :name end @@ -156,11 +165,6 @@ ActiveRecord::Schema.define do t.integer :company_id end - create_table :iris, :force => true do |t| - t.integer :eye - t.string :color - end - create_table :customers, :force => true do |t| t.string :name t.integer :balance, :default => 0 @@ -199,18 +203,6 @@ ActiveRecord::Schema.define do t.integer :car_id end - create_table :eyes, :force => true do |t| - end - - create_table :tyres, :force => true do |t| - t.integer :car_id - end - - create_table :bulbs, :force => true do |t| - t.integer :car_id - t.string :name - end - create_table :entrants, :force => true do |t| t.string :name, :null => false t.integer :course_id, :null => false @@ -226,6 +218,9 @@ ActiveRecord::Schema.define do t.string :title, :limit => 5 end + create_table :eyes, :force => true do |t| + end + create_table :funny_jokes, :force => true do |t| t.string :name end @@ -235,13 +230,8 @@ ActiveRecord::Schema.define do t.string :info end - create_table :invoices, :force => true do |t| - t.integer :balance - t.datetime :updated_at - end - - create_table :items, :force => true do |t| - t.column :name, :string + create_table :guids, :force => true do |t| + t.column :key, :string end create_table :inept_wizards, :force => true do |t| @@ -250,6 +240,26 @@ ActiveRecord::Schema.define do t.column :type, :string end + create_table :integer_limits, :force => true do |t| + t.integer :"c_int_without_limit" + (1..8).each do |i| + t.integer :"c_int_#{i}", :limit => i + end + end + + create_table :invoices, :force => true do |t| + t.integer :balance + t.datetime :updated_at + end + + create_table :iris, :force => true do |t| + t.references :eye + t.string :color + end + + create_table :items, :force => true do |t| + t.column :name, :string + end create_table :jobs, :force => true do |t| t.integer :ideal_reference_id @@ -306,13 +316,6 @@ ActiveRecord::Schema.define do t.string :name end - create_table :references, :force => true do |t| - t.integer :person_id - t.integer :job_id - t.boolean :favourite - t.integer :lock_version, :default => 0 - end - create_table :minivans, :force => true, :id => false do |t| t.string :minivan_id t.string :name @@ -375,7 +378,6 @@ ActiveRecord::Schema.define do t.column :happy_at, :datetime end - create_table :paint_colors, :force => true do |t| t.integer :non_poly_one_id end @@ -462,6 +464,13 @@ ActiveRecord::Schema.define do t.boolean :skimmer, :default => false end + create_table :references, :force => true do |t| + t.integer :person_id + t.integer :job_id + t.boolean :favourite + t.integer :lock_version, :default => 0 + end + create_table :shape_expressions, :force => true do |t| t.string :paint_type t.integer :paint_id @@ -506,6 +515,18 @@ ActiveRecord::Schema.define do t.integer :book_id end + create_table :tags, :force => true do |t| + t.column :name, :string + t.column :taggings_count, :integer, :default => 0 + end + + create_table :taggings, :force => true do |t| + t.column :tag_id, :integer + t.column :super_tag_id, :integer + t.column :taggable_type, :string + t.column :taggable_id, :integer + end + create_table :tasks, :force => true do |t| t.datetime :starting t.datetime :ending @@ -533,18 +554,6 @@ ActiveRecord::Schema.define do t.string :group end - create_table :taggings, :force => true do |t| - t.column :tag_id, :integer - t.column :super_tag_id, :integer - t.column :taggable_type, :string - t.column :taggable_id, :integer - end - - create_table :tags, :force => true do |t| - t.column :name, :string - t.column :taggings_count, :integer, :default => 0 - end - create_table :toys, :primary_key => :toy_id ,:force => true do |t| t.string :name t.integer :pet_id, :integer @@ -564,6 +573,10 @@ ActiveRecord::Schema.define do t.column :looter_type, :string end + create_table :tyres, :force => true do |t| + t.integer :car_id + end + create_table :variants, :force => true do |t| t.references :product t.string :name @@ -581,17 +594,6 @@ ActiveRecord::Schema.define do create_table(t, :force => true) { } end - create_table :guids, :force => true do |t| - t.column :key, :string - end - - create_table :integer_limits, :force => true do |t| - t.integer :"c_int_without_limit" - (1..8).each do |i| - t.integer :"c_int_#{i}", :limit => i - end - end - # NOTE - the following 4 tables are used by models that have :inverse_of options on the associations create_table :men, :force => true do |t| t.string :name @@ -647,7 +649,6 @@ ActiveRecord::Schema.define do t.string :name end - except 'SQLite' do # fk_test_has_fk should be before fk_test_has_pk create_table :fk_test_has_fk, :force => true do |t| -- cgit v1.2.3 From 40491b465423879d23e979b91a13f06a2eec6522 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 22 Oct 2010 10:35:02 -0700 Subject: removing space errors --- activerecord/lib/active_record/associations.rb | 90 +++++++++++++------------- 1 file changed, 45 insertions(+), 45 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index affa2fbcaf..59d328f207 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -2011,7 +2011,7 @@ module ActiveRecord association_proxy = record.send("set_#{join_part.reflection.name}_target", association) association_proxy.__send__(:set_inverse_instance, association, record) end - + # A JoinPart represents a part of a JoinDependency. It is an abstract class, inherited # by JoinBase and JoinAssociation. A JoinBase represents the Active Record which # everything else is being joined onto. A JoinAssociation represents an association which @@ -2023,38 +2023,38 @@ module ActiveRecord # this is the actual base model, for a JoinAssociation this is the target model of the # association. attr_reader :active_record - + delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :arel_engine, :to => :active_record - + def initialize(active_record) @active_record = active_record @cached_record = {} end - + def ==(other) raise NotImplementedError end - + # An Arel::Table for the active_record def table raise NotImplementedError end - + # The prefix to be used when aliasing columns in the active_record's table def aliased_prefix raise NotImplementedError end - + # The alias for the active_record's table def aliased_table_name raise NotImplementedError end - + # The alias for the primary key of the active_record's table def aliased_primary_key "#{aliased_prefix}_r0" end - + # An array of [column_name, alias] pairs for the table def column_names_with_alias unless defined?(@column_names_with_alias) @@ -2084,7 +2084,7 @@ module ActiveRecord class JoinBase < JoinPart # :nodoc: # Extra joins provided when the JoinDependency was created attr_reader :table_joins - + def initialize(active_record, joins = nil) super(active_record) @table_joins = joins @@ -2112,39 +2112,39 @@ module ActiveRecord class JoinAssociation < JoinPart # :nodoc: # The reflection of the association represented attr_reader :reflection - + # The JoinDependency object which this JoinAssociation exists within. This is mainly # relevant for generating aliases which do not conflict with other joins which are # part of the query. attr_reader :join_dependency - + # A JoinBase instance representing the active record we are joining onto. # (So in Author.has_many :posts, the Author would be that base record.) attr_reader :parent - + # What type of join will be generated, either Arel::InnerJoin (default) or Arel::OuterJoin attr_accessor :join_type - + # These implement abstract methods from the superclass attr_reader :aliased_prefix, :aliased_table_name - + delegate :options, :through_reflection, :source_reflection, :to => :reflection delegate :table, :table_name, :to => :parent, :prefix => true def initialize(reflection, join_dependency, parent = nil) reflection.check_validity! - + if reflection.options[:polymorphic] raise EagerLoadPolymorphicError.new(reflection) end super(reflection.klass) - + @reflection = reflection @join_dependency = join_dependency @parent = parent @join_type = Arel::InnerJoin - + # This must be done eagerly upon initialisation because the alias which is produced # depends on the state of the join dependency, but we want it to work the same way # every time. @@ -2162,7 +2162,7 @@ module ActiveRecord self.parent == join_part end end - + def join_to(relation) send("join_#{reflection.macro}_to", relation) end @@ -2171,14 +2171,14 @@ module ActiveRecord self.join_type = Arel::OuterJoin joining_relation.joins(self) end - + def table @table ||= Arel::Table.new( table_name, :as => aliased_table_name, :engine => arel_engine, :columns => active_record.columns ) end - + # More semantic name given we are talking about associations alias_method :target_table, :table @@ -2214,43 +2214,43 @@ module ActiveRecord end private - + def allocate_aliases @aliased_prefix = "t#{ join_dependency.join_parts.size }" @aliased_table_name = aliased_table_name_for(table_name) - + if reflection.macro == :has_and_belongs_to_many @aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join") elsif [:has_many, :has_one].include?(reflection.macro) && reflection.options[:through] @aliased_join_table_name = aliased_table_name_for(reflection.through_reflection.klass.table_name, "_join") end end - + def process_conditions(conditions, table_name) Arel.sql(interpolate_sql(sanitize_sql(conditions, table_name))) end - + def join_target_table(relation, *conditions) relation = relation.join(target_table, join_type) - + # If the target table is an STI model then we must be sure to only include records of # its type and its sub-types. unless active_record.descends_from_active_record? sti_column = target_table[active_record.inheritance_column] - + sti_condition = sti_column.eq(active_record.sti_name) active_record.descendants.each do |subclass| sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name)) end - + conditions << sti_condition end - + # If the reflection has conditions, add them if options[:conditions] conditions << process_conditions(options[:conditions], aliased_table_name) end - + relation = relation.on(*conditions) end @@ -2259,16 +2259,16 @@ module ActiveRecord options[:join_table], :engine => arel_engine, :as => @aliased_join_table_name ) - + fk = options[:foreign_key] || reflection.active_record.to_s.foreign_key klass_fk = options[:association_foreign_key] || reflection.klass.to_s.foreign_key - + relation = relation.join(join_table, join_type) relation = relation.on( join_table[fk]. eq(parent_table[reflection.active_record.primary_key]) ) - + join_target_table( relation, target_table[reflection.klass.primary_key]. @@ -2284,7 +2284,7 @@ module ActiveRecord else foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key primary_key = options[:primary_key] || parent.primary_key - + join_target_table( relation, target_table[foreign_key]. @@ -2293,20 +2293,20 @@ module ActiveRecord end end alias :join_has_one_to :join_has_many_to - + def join_has_many_through_to(relation) join_table = Arel::Table.new( through_reflection.klass.table_name, :engine => arel_engine, :as => @aliased_join_table_name ) - + jt_conditions = [] jt_foreign_key = first_key = second_key = nil if through_reflection.options[:as] # has_many :through against a polymorphic join as_key = through_reflection.options[:as].to_s jt_foreign_key = as_key + '_id' - + jt_conditions << join_table[as_key + '_type']. eq(parent.active_record.base_class.name) @@ -2331,10 +2331,10 @@ module ActiveRecord end when :belongs_to first_key = primary_key - + if reflection.options[:source_type] second_key = source_reflection.association_foreign_key - + jt_conditions << join_table[reflection.source_reflection.options[:foreign_type]]. eq(reflection.options[:source_type]) @@ -2342,23 +2342,23 @@ module ActiveRecord second_key = source_reflection.primary_key_name end end - + jt_conditions << parent_table[parent.primary_key]. eq(join_table[jt_foreign_key]) - + if through_reflection.options[:conditions] jt_conditions << process_conditions(through_reflection.options[:conditions], aliased_table_name) end - + relation = relation.join(join_table, join_type).on(*jt_conditions) - + join_target_table( relation, target_table[first_key].eq(join_table[second_key]) ) end - + def join_has_many_polymorphic_to(relation) join_target_table( relation, @@ -2372,7 +2372,7 @@ module ActiveRecord def join_belongs_to_to(relation) foreign_key = options[:foreign_key] || reflection.primary_key_name primary_key = options[:primary_key] || reflection.klass.primary_key - + join_target_table( relation, target_table[primary_key].eq(parent_table[foreign_key]) -- cgit v1.2.3 From 1ce76db9186a0b4ada341369f682bb8a0cd8da3b Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 22 Oct 2010 13:31:44 -0700 Subject: if it responds to :usec, it should also "act like" a time --- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index c0cc7ba20d..69bf2c0dcd 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -121,7 +121,7 @@ module ActiveRecord # Quote date/time values for use in SQL input. Includes microseconds # if the value is a Time responding to usec. def quoted_date(value) #:nodoc: - if value.acts_like?(:time) && value.respond_to?(:usec) + if value.respond_to?(:usec) "#{super}.#{sprintf("%06d", value.usec)}" else super -- cgit v1.2.3 From ee71a3fbfc6fb8112a58a8fcae31a1c2a423ef3f Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 22 Oct 2010 13:33:59 -0700 Subject: removing call to deprecated API, this test is outside AR responsibilities --- activerecord/test/cases/relations_test.rb | 7 ------- 1 file changed, 7 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 222819cbe5..b01a8bbef1 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -25,13 +25,6 @@ class RelationTest < ActiveRecord::TestCase assert_no_queries { car.engines.length } end - def test_apply_relation_as_where_id - posts = Post.arel_table - post_authors = posts.where(posts[:author_id].eq(1)).project(posts[:id]) - assert_equal 5, post_authors.to_a.size - assert_equal 5, Post.where(:id => post_authors).size - end - def test_dynamic_finder x = Post.where('author_id = ?', 1) assert x.klass.respond_to?(:find_by_id), '@klass should handle dynamic finders' -- cgit v1.2.3 From 3e4ede81d6b1ff3ea89eca33715a5a0d2f524dcf Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sat, 23 Oct 2010 20:15:47 -0700 Subject: removing unused variable --- activerecord/lib/active_record/schema_dumper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index e30b481fe1..ba3dd49e7e 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -83,7 +83,7 @@ HEADER # first dump primary key column if @connection.respond_to?(:pk_and_sequence_for) - pk, pk_seq = @connection.pk_and_sequence_for(table) + pk = @connection.pk_and_sequence_for(table).first elsif @connection.respond_to?(:primary_key) pk = @connection.primary_key(table) end -- cgit v1.2.3 From 6c998a79c8d9f4458d8a8b940a6478d6b55959a0 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sat, 23 Oct 2010 20:19:13 -0700 Subject: removing dead code --- activerecord/lib/active_record/association_preload.rb | 2 -- 1 file changed, 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index e6b367790b..4f0684d753 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -256,8 +256,6 @@ module ActiveRecord end def preload_through_records(records, reflection, through_association) - through_reflection = reflections[through_association] - through_records = [] if reflection.options[:source_type] interface = reflection.source_reflection.options[:foreign_type] -- cgit v1.2.3 From 5685a5c41c770903b4c01af2508cd448108a036c Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sat, 23 Oct 2010 20:29:25 -0700 Subject: refactor to remove `through_records` --- activerecord/lib/active_record/association_preload.rb | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index 4f0684d753..c2a71487dc 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -256,7 +256,6 @@ module ActiveRecord end def preload_through_records(records, reflection, through_association) - through_records = [] if reflection.options[:source_type] interface = reflection.source_reflection.options[:foreign_type] preload_options = {:conditions => ["#{connection.quote_column_name interface} = ?", reflection.options[:source_type]]} @@ -265,16 +264,15 @@ module ActiveRecord records.first.class.preload_associations(records, through_association, preload_options) # Dont cache the association - we would only be caching a subset - records.each do |record| + records.map { |record| proxy = record.send(through_association) if proxy.respond_to?(:target) - through_records.concat Array.wrap(proxy.target) - proxy.reset + Array.wrap(proxy.target).tap { proxy.reset } else # this is a has_one :through reflection - through_records << proxy if proxy + [proxy].compact end - end + }.flatten(1) else options = {} options[:include] = reflection.options[:include] || reflection.options[:source] if reflection.options[:conditions] @@ -282,11 +280,10 @@ module ActiveRecord options[:conditions] = reflection.options[:conditions] records.first.class.preload_associations(records, through_association, options) - records.each do |record| - through_records.concat Array.wrap(record.send(through_association)) - end + records.map { |record| + Array.wrap(record.send(through_association)) + }.flatten(1) end - through_records end def preload_belongs_to_association(records, reflection, preload_options={}) -- cgit v1.2.3 From f61f75876136f80b455bad0d5274190a3f9df290 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 26 Oct 2010 09:59:05 -0700 Subject: reducing the number of parameters to select() --- activerecord/test/connections/native_oracle/connection.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/connections/native_oracle/connection.rb b/activerecord/test/connections/native_oracle/connection.rb index 9a717018b2..c942036128 100644 --- a/activerecord/test/connections/native_oracle/connection.rb +++ b/activerecord/test/connections/native_oracle/connection.rb @@ -37,10 +37,10 @@ Course.establish_connection 'arunit2' ActiveRecord::Base.connection.class.class_eval do IGNORED_SELECT_SQL = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from ((all|user)_tab_columns|(all|user)_triggers|(all|user)_constraints)/im] - def select_with_query_record(sql, name = nil, return_column_names = false) + def select_with_query_record(sql, name = nil) $queries_executed ||= [] $queries_executed << sql unless IGNORED_SELECT_SQL.any? { |r| sql =~ r } - select_without_query_record(sql, name, return_column_names) + select_without_query_record(sql, name) end alias_method_chain :select, :query_record -- cgit v1.2.3 From c376fd488c64dd2241fa8bec18bb712105e0e1b3 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 26 Oct 2010 13:29:39 -0700 Subject: Revert "removing unused variable" This reverts commit 3e4ede81d6b1ff3ea89eca33715a5a0d2f524dcf. --- activerecord/lib/active_record/schema_dumper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index ba3dd49e7e..e30b481fe1 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -83,7 +83,7 @@ HEADER # first dump primary key column if @connection.respond_to?(:pk_and_sequence_for) - pk = @connection.pk_and_sequence_for(table).first + pk, pk_seq = @connection.pk_and_sequence_for(table) elsif @connection.respond_to?(:primary_key) pk = @connection.primary_key(table) end -- cgit v1.2.3 From ef0cf143a939772edd89f90b0a095c8eb65139d2 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 12 Oct 2010 13:32:48 -0700 Subject: adding the abstract method --- .../connection_adapters/abstract/database_statements.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'activerecord') 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 646a78622c..cf67185b83 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -39,6 +39,12 @@ module ActiveRecord end undef_method :execute + # Executes +sql+ statement in the context of this connection using + # +bind_values+ as the bind substitutes. +name+ is logged along with + # the executed +sql+ statement. + def exec(sql, name = nil, binds = []) + end + # Returns the last auto-generated ID from the affected table. def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) insert_sql(sql, name, pk, id_value, sequence_name) -- cgit v1.2.3 From 497218d5db8f7c3887c3861bdbe318331b77fed7 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 12 Oct 2010 13:57:05 -0700 Subject: adding bind value substitution --- .../lib/active_record/connection_adapters/abstract_adapter.rb | 6 ++++++ activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb | 9 +++++++++ 2 files changed, 15 insertions(+) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index d8c92d0ad3..bcbef526bd 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -97,6 +97,12 @@ module ActiveRecord quote_column_name(name) end + # Returns a bind substitution value given a +column+ and list of current + # +bind_values+ + def substitute_for(column, bind_values) + Arel.sql '?' + end + # REFERENTIAL INTEGRITY ==================================== # Override to turn off referential integrity while executing &block. diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 934cf72f72..f4a856e42a 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -51,6 +51,15 @@ module ActiveRecord :timeout => 100 assert_equal 'UTF-8', conn.encoding end + + def test_bind_value_substitute + conn = Base.sqlite3_connection :database => ':memory:', + :adapter => 'sqlite3', + :timeout => 100 + + bind_param = conn.substitute_for('foo', []) + assert_equal Arel.sql('?'), bind_param + end end end end -- cgit v1.2.3 From 6ceffb8178b6d419d4453e1e24d4138215c35217 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 12 Oct 2010 14:45:30 -0700 Subject: adding bind_values to relations --- activerecord/lib/active_record/relation.rb | 2 +- activerecord/lib/active_record/relation/query_methods.rb | 9 ++++++++- activerecord/test/cases/relations_test.rb | 9 +++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index f129b54f9a..4c75dff891 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -5,7 +5,7 @@ module ActiveRecord class Relation JoinOperation = Struct.new(:relation, :join_class, :on) ASSOCIATION_METHODS = [:includes, :eager_load, :preload] - MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having] + MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind] SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from] include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 64d2cb0203..9c399d3333 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -6,7 +6,8 @@ module ActiveRecord extend ActiveSupport::Concern attr_accessor :includes_values, :eager_load_values, :preload_values, - :select_values, :group_values, :order_values, :joins_values, :where_values, :having_values, + :select_values, :group_values, :order_values, :joins_values, + :where_values, :having_values, :bind_values, :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value, :from_value def includes(*args) @@ -62,6 +63,12 @@ module ActiveRecord relation end + def bind(value) + relation = clone + relation.bind_values += [value] + relation + end + def where(opts, *rest) relation = clone relation.where_values += build_where(opts, rest) unless opts.blank? diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index b01a8bbef1..d2ccc1480a 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -19,6 +19,15 @@ class RelationTest < ActiveRecord::TestCase fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments, :taggings, :cars + def test_bind_values + relation = Post.scoped + assert_equal [], relation.bind_values + + relation2 = relation.bind 'foo' + assert_equal %w{ foo }, relation2.bind_values + assert_equal [], relation.bind_values + end + def test_two_named_scopes_with_includes_should_not_drop_any_include car = Car.incl_engines.incl_tyres.first assert_no_queries { car.tyres.length } -- cgit v1.2.3 From cc468d3ec81d6f1298fca91c0549584b36dafcc6 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 12 Oct 2010 15:57:26 -0700 Subject: exec returns an AR::Result --- activerecord/lib/active_record/base.rb | 4 +-- .../abstract/database_statements.rb | 6 ++-- .../connection_adapters/abstract_adapter.rb | 1 + .../connection_adapters/sqlite_adapter.rb | 26 ++++++++++++++-- activerecord/lib/active_record/relation.rb | 2 +- activerecord/lib/active_record/result.rb | 30 +++++++++++++++++++ .../cases/adapters/sqlite3/sqlite3_adapter_test.rb | 35 ++++++++++++++++++++++ activerecord/test/cases/helper.rb | 8 +++++ 8 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 activerecord/lib/active_record/result.rb (limited to 'activerecord') 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] # > [#"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 # => # + 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 { -- cgit v1.2.3 From 853d39e8540a8af4c42592a172d4cf4f9c0f4e9f Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 12 Oct 2010 16:03:11 -0700 Subject: removing some uses of execute --- .../lib/active_record/connection_adapters/sqlite_adapter.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 75bf579764..0d828046d7 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -173,9 +173,7 @@ module ActiveRecord alias :create :insert_sql def select_rows(sql, name = nil) - execute(sql, name).map do |row| - (0...(row.size / 2)).map { |i| row[i] } - end + exec(sql, name).rows end def begin_db_transaction #:nodoc: @@ -199,7 +197,7 @@ module ActiveRecord WHERE type = 'table' AND NOT name = 'sqlite_sequence' SQL - execute(sql, name).map do |row| + exec(sql, name).map do |row| row['name'] end end -- cgit v1.2.3 From e60fecd8296121b27530d6b2425830e605740382 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 12 Oct 2010 16:04:55 -0700 Subject: removing more calls to execute --- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 0d828046d7..1a5ce0c9a2 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -209,12 +209,12 @@ module ActiveRecord end def indexes(table_name, name = nil) #:nodoc: - execute("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row| + exec("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row| IndexDefinition.new( table_name, row['name'], row['unique'].to_i != 0, - execute("PRAGMA index_info('#{row['name']}')").map { |col| + exec("PRAGMA index_info('#{row['name']}')").map { |col| col['name'] }) end -- cgit v1.2.3 From 7023d73f2649cde7a985b02de2d99f15c62d831b Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 12 Oct 2010 16:08:44 -0700 Subject: removing more execute calls --- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 1a5ce0c9a2..e515b52d6d 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -228,11 +228,11 @@ module ActiveRecord end def remove_index!(table_name, index_name) #:nodoc: - execute "DROP INDEX #{quote_column_name(index_name)}" + exec "DROP INDEX #{quote_column_name(index_name)}" end def rename_table(name, new_name) - execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}" + exec "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}" end # See: http://www.sqlite.org/lang_altertable.html -- cgit v1.2.3 From 77d548a6d339be894c5369c7c0ef78af8a3b5af3 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 12 Oct 2010 16:14:44 -0700 Subject: removing another execute --- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index e515b52d6d..8ae9d2afe4 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -269,7 +269,7 @@ module ActiveRecord def change_column_null(table_name, column_name, null, default = nil) unless null || default.nil? - execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") + exec("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") end alter_table(table_name) do |definition| definition[column_name].null = null -- cgit v1.2.3 From 76d08057860d61cf16893bbfecbc7c7ccec74386 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 12 Oct 2010 16:22:38 -0700 Subject: stop calling execute directly on the database connection --- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 8ae9d2afe4..0ea45dd61d 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -387,11 +387,11 @@ module ActiveRecord quoted_columns = columns.map { |col| quote_column_name(col) } * ',' quoted_to = quote_table_name(to) - @connection.execute "SELECT * FROM #{quote_table_name(from)}" do |row| + exec("SELECT * FROM #{quote_table_name(from)}").each do |row| sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES (" sql << columns.map {|col| quote row[column_mappings[col]]} * ', ' sql << ')' - @connection.execute sql + exec sql end end -- cgit v1.2.3 From d7207cf504b67243c0b7debaff70d948bb8be7a8 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 12 Oct 2010 16:39:29 -0700 Subject: type casting bound value based on column associated with value --- .../connection_adapters/sqlite_adapter.rb | 4 +++- .../cases/adapters/sqlite3/sqlite3_adapter_test.rb | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 0ea45dd61d..6598feec62 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -146,7 +146,9 @@ module ActiveRecord stmt = cache[:stmt] cols = cache[:cols] ||= stmt.columns stmt.reset! - stmt.bind_params bind_values.map { |col, val| val } + stmt.bind_params bind_values.map { |col, val| + col ? col.type_cast(val) : val + } end ActiveRecord::Result.new(cols, stmt.to_a) diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 52630c5ad4..969029db51 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -95,6 +95,24 @@ module ActiveRecord assert_equal [[1, 'foo']], result.rows end + + def test_exec_typecasts_bind_vals + 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")') + column = conn.columns('ex').find { |col| col.name == 'id' } + + result = conn.exec( + 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']]) + + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length + + assert_equal [[1, 'foo']], result.rows + end end end end -- cgit v1.2.3 From 54ff59f3ba4c299ebb0b517ae1a3efb5859ab1c7 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 12 Oct 2010 16:58:18 -0700 Subject: refactoring tests --- .../cases/adapters/sqlite3/sqlite3_adapter_test.rb | 56 ++++++++-------------- 1 file changed, 20 insertions(+), 36 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 969029db51..973c51f3d7 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -3,6 +3,12 @@ require "cases/helper" module ActiveRecord module ConnectionAdapters class SQLite3AdapterTest < ActiveRecord::TestCase + def setup + @conn = Base.sqlite3_connection :database => ':memory:', + :adapter => 'sqlite3', + :timeout => 100 + end + def test_connection_no_db assert_raises(ArgumentError) do Base.sqlite3_connection {} @@ -38,42 +44,28 @@ module ActiveRecord end def test_connect - conn = Base.sqlite3_connection :database => ':memory:', - :adapter => 'sqlite3', - :timeout => 100 - assert conn, 'should have connection' + assert @conn, 'should have connection' end # sqlite3 defaults to UTF-8 encoding def test_encoding - conn = Base.sqlite3_connection :database => ':memory:', - :adapter => 'sqlite3', - :timeout => 100 - assert_equal 'UTF-8', conn.encoding + assert_equal 'UTF-8', @conn.encoding end def test_bind_value_substitute - conn = Base.sqlite3_connection :database => ':memory:', - :adapter => 'sqlite3', - :timeout => 100 - - bind_param = conn.substitute_for('foo', []) + 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') + @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') + @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 @@ -81,13 +73,9 @@ module ActiveRecord 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( + @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 @@ -97,15 +85,11 @@ module ActiveRecord end def test_exec_typecasts_bind_vals - 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")') - column = conn.columns('ex').find { |col| col.name == 'id' } + @conn.exec('create table ex(id int, data string)') + @conn.exec('INSERT INTO ex (id, data) VALUES (1, "foo")') + column = @conn.columns('ex').find { |col| col.name == 'id' } - result = conn.exec( + result = @conn.exec( 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']]) assert_equal 1, result.rows.length -- cgit v1.2.3 From 5abebfb569dcf69847a473c337e07e25f88d63a4 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 12 Oct 2010 16:58:39 -0700 Subject: clearing statements on disconnect and reset --- .../lib/active_record/connection_adapters/sqlite_adapter.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 6598feec62..0c58c57390 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -80,9 +80,15 @@ module ActiveRecord def disconnect! super + @statements.clear @connection.close rescue nil end + def reset! + @statements.clear + super + end + def supports_count_distinct? #:nodoc: sqlite_version >= '3.2.6' end -- cgit v1.2.3 From 9c7e2e4117018ef462efc1e06a45b046b466789f Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 12 Oct 2010 17:04:30 -0700 Subject: find_one uses prepared statement cache --- activerecord/lib/active_record/base.rb | 1 + activerecord/lib/active_record/relation/finder_methods.rb | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index db7f9b7974..492a807ff5 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -718,6 +718,7 @@ module ActiveRecord #:nodoc: # end # end def reset_column_information + connection.reset! undefine_attribute_methods @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil @arel_engine = @relation = @arel_table = nil diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index b763e22ec6..fe1ef2e2e3 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -288,7 +288,12 @@ module ActiveRecord def find_one(id) id = id.id if ActiveRecord::Base === id - record = where(primary_key.eq(id)).first + column = primary_key.column + + substitute = connection.substitute_for(column, @bind_values) + relation = where(primary_key.eq(substitute)) + relation.bind_values = [[column, id]] + record = relation.first unless record conditions = arel.where_sql -- cgit v1.2.3 From 77b1193ac148aec3fd08fd21b26827428a1449bb Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 13 Oct 2010 10:23:00 -0700 Subject: mysql tests are mostly passing --- .../connection_adapters/mysql_adapter.rb | 82 +++++++++++++++++----- .../test/cases/adapters/mysql/connection_test.rb | 58 +++++++++++++++ activerecord/test/cases/attribute_methods_test.rb | 6 +- 3 files changed, 126 insertions(+), 20 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index a4b336dfaf..c31326d891 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -3,6 +3,27 @@ require 'active_support/core_ext/kernel/requires' require 'active_support/core_ext/object/blank' require 'set' +begin + require 'mysql' +rescue LoadError + raise "!!! Missing the mysql gem. Add it to your Gemfile: gem 'mysql'" +end + +unless defined?(Mysql::Result) && Mysql::Result.method_defined?(:each_hash) + raise "!!! Outdated mysql gem. Upgrade to 2.8.1 or later. In your Gemfile: gem 'mysql', '2.8.1'. Or use gem 'mysql2'" +end + +class Mysql + class Time + ### + # This monkey patch is for test_additional_columns_from_join_table + + def to_date + Date.new(year, month, day) + end + end +end + module ActiveRecord class Base # Establishes a connection to the database that's used by all Active Record objects. @@ -15,18 +36,6 @@ module ActiveRecord password = config[:password].to_s database = config[:database] - unless defined? Mysql - begin - require 'mysql' - rescue LoadError - raise "!!! Missing the mysql2 gem. Add it to your Gemfile: gem 'mysql2'" - end - - unless defined?(Mysql::Result) && Mysql::Result.method_defined?(:each_hash) - raise "!!! Outdated mysql gem. Upgrade to 2.8.1 or later. In your Gemfile: gem 'mysql', '2.8.1'. Or use gem 'mysql2'" - end - end - mysql = Mysql.init mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslca] || config[:sslkey] @@ -39,6 +48,30 @@ module ActiveRecord module ConnectionAdapters class MysqlColumn < Column #:nodoc: + class << self + def string_to_time(value) + return super unless Mysql::Time === value + new_time( + value.year, + value.month, + value.day, + value.hour, + value.minute, + value.second, + value.second_part) + end + + def string_to_dummy_time(v) + return super unless Mysql::Time === v + new_time(2000, 01, 01, v.hour, v.minute, v.second, v.second_part) + end + + def string_to_date(v) + return super unless Mysql::Time === v + new_date(v.year, v.month, v.day) + end + end + def extract_default(default) if sql_type =~ /blob/i || type == :text if default.blank? @@ -280,6 +313,24 @@ module ActiveRecord rows end + def exec(sql, name = 'SQL', bind_values = []) + log(sql, name) do + stmt = @connection.prepare(sql) + stmt.execute(*bind_values.map { |col, val| + col ? col.type_cast(val) : val + }) + result = nil + if metadata = stmt.result_metadata + cols = metadata.fetch_fields.map { |field| field.name } + values = [] + stmt.each { |thing| values << thing } + result = ActiveRecord::Result.new(cols, values) + end + stmt.close + result + end + end + # Executes an SQL query and returns a MySQL::Result object. Note that you have to free # the Result object after you're done using it. def execute(sql, name = nil) #:nodoc: @@ -614,12 +665,9 @@ module ActiveRecord execute("SET SQL_AUTO_IS_NULL=0", :skip_logging) end - def select(sql, name = nil) + def select(sql, name = nil, binds = []) @connection.query_with_result = true - result = execute(sql, name) - rows = [] - result.each_hash { |row| rows << row } - result.free + rows = exec(sql, name, binds).to_a @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped rows end diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index f76a23a8ad..67bd8ec7e0 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -43,6 +43,64 @@ class MysqlConnectionTest < ActiveRecord::TestCase assert @connection.active? end + def test_bind_value_substitute + bind_param = @connection.substitute_for('foo', []) + assert_equal Arel.sql('?'), bind_param + end + + def test_exec_no_binds + @connection.exec('drop table if exists ex') + @connection.exec(<<-eosql) + CREATE TABLE `ex` (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY, + `data` varchar(255)) + eosql + result = @connection.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 + + @connection.exec('INSERT INTO ex (id, data) VALUES (1, "foo")') + result = @connection.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 + @connection.exec('drop table if exists ex') + @connection.exec(<<-eosql) + CREATE TABLE `ex` (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY, + `data` varchar(255)) + eosql + @connection.exec('INSERT INTO ex (id, data) VALUES (1, "foo")') + result = @connection.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 + + def test_exec_typecasts_bind_vals + @connection.exec('drop table if exists ex') + @connection.exec(<<-eosql) + CREATE TABLE `ex` (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY, + `data` varchar(255)) + eosql + @connection.exec('INSERT INTO ex (id, data) VALUES (1, "foo")') + column = @connection.columns('ex').find { |col| col.name == 'id' } + + result = @connection.exec( + 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']]) + + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length + + assert_equal [[1, 'foo']], result.rows + end + # Test that MySQL allows multiple results for stored procedures if Mysql.const_defined?(:CLIENT_MULTI_RESULTS) def test_multi_results diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 1750bf004a..ab9a65944f 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -103,7 +103,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase if current_adapter?(:MysqlAdapter) def test_read_attributes_before_type_cast_on_boolean bool = Boolean.create({ "value" => false }) - assert_equal "0", bool.reload.attributes_before_type_cast["value"] + assert_equal 0, bool.reload.attributes_before_type_cast["value"] end end @@ -112,7 +112,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase developer = Developer.find(:first) # Oracle adapter returns Time before type cast unless current_adapter?(:OracleAdapter) - assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"] + assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"].to_s else assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"].to_s(:db) @@ -121,7 +121,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal developer.created_at, nil developer.created_at = "2010-03-21 21:23:32" - assert_equal developer.created_at_before_type_cast, "2010-03-21 21:23:32" + assert_equal developer.created_at_before_type_cast.to_s, "2010-03-21 21:23:32" assert_equal developer.created_at, Time.parse("2010-03-21 21:23:32") end end -- cgit v1.2.3 From d6b16bbaf705237f68980c4b0bd3b407225d8aa0 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 13 Oct 2010 10:37:43 -0700 Subject: one more mysql test left! --- activerecord/lib/active_record/base.rb | 2 +- .../lib/active_record/connection_adapters/abstract_adapter.rb | 7 +++++++ .../lib/active_record/connection_adapters/sqlite_adapter.rb | 5 ++--- activerecord/test/cases/query_cache_test.rb | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 492a807ff5..825b86f068 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -718,7 +718,7 @@ module ActiveRecord #:nodoc: # end # end def reset_column_information - connection.reset! + connection.clear_cache! undefine_attribute_methods @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil @arel_engine = @relation = @arel_table = nil diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index a36a182a9e..6f014bf9f1 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -142,6 +142,13 @@ module ActiveRecord # this should be overridden by concrete adapters end + ### + # Clear any caching the database adapter may be doing, for example + # clearing the prepared statement cache. This is database specific. + def clear_cache! + # this should be overridden by concrete adapters + end + # Returns true if its required to reload the connection between requests for development mode. # This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite. def requires_reloading? diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 0c58c57390..97f595af66 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -80,13 +80,12 @@ module ActiveRecord def disconnect! super - @statements.clear + clear_cache! @connection.close rescue nil end - def reset! + def clear_cache! @statements.clear - super end def supports_count_distinct? #:nodoc: diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 594db1d0ab..bdd0cc6b7b 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -57,7 +57,7 @@ class QueryCacheTest < ActiveRecord::TestCase # Oracle adapter returns count() as Fixnum or Float if current_adapter?(:OracleAdapter) assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") - elsif current_adapter?(:SQLite3Adapter) && SQLite3::Version::VERSION > '1.2.5' or current_adapter?(:Mysql2Adapter) + elsif current_adapter?(:SQLite3Adapter) && SQLite3::Version::VERSION > '1.2.5' || current_adapter?(:Mysql2Adapter) || current_adapter?(:MysqlAdapter) # Future versions of the sqlite3 adapter will return numeric assert_instance_of Fixnum, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") -- cgit v1.2.3 From d4b0bcb88e058a642b193b9e008b43a32fd64d64 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 13 Oct 2010 11:09:21 -0700 Subject: all mysql tests are passing --- .../active_record/connection_adapters/mysql_adapter.rb | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index c31326d891..a4cd2cd964 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -315,11 +315,11 @@ module ActiveRecord def exec(sql, name = 'SQL', bind_values = []) log(sql, name) do + result = nil stmt = @connection.prepare(sql) stmt.execute(*bind_values.map { |col, val| col ? col.type_cast(val) : val }) - result = nil if metadata = stmt.result_metadata cols = metadata.fetch_fields.map { |field| field.name } values = [] @@ -331,6 +331,19 @@ module ActiveRecord end end + def exec_without_stmt(sql, name = 'SQL') # :nodoc: + # Some queries, like SHOW CREATE TABLE don't work through the prepared + # statement API. For those queries, we need to use this method. :'( + log(sql, name) do + result = @connection.query(sql) + cols = result.fetch_fields.map { |field| field.name } + values = [] + result.each { |row| values << row } + result.free + ActiveRecord::Result.new(cols, values) + end + end + # Executes an SQL query and returns a MySQL::Result object. Note that you have to free # the Result object after you're done using it. def execute(sql, name = nil) #:nodoc: @@ -411,7 +424,8 @@ module ActiveRecord select_all(sql).map do |table| table.delete('Table_type') - select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n" + sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}" + exec_without_stmt(sql).first['Create Table'] + ";\n\n" end.join("") end -- cgit v1.2.3 From 050d7d3e4979c88e8df46b69e6953599c09f6c18 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 13 Oct 2010 11:18:11 -0700 Subject: statements are cached, cache is cleared on reconnect --- .../connection_adapters/mysql_adapter.rb | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index a4cd2cd964..1c32f64d92 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -194,6 +194,7 @@ module ActiveRecord super(connection, logger) @connection_options, @config = connection_options, config @quoted_column_names, @quoted_table_names = {}, {} + @statements = {} connect end @@ -285,6 +286,7 @@ module ActiveRecord def reconnect! disconnect! + clear_cache! connect end @@ -313,10 +315,23 @@ module ActiveRecord rows end + def clear_cache! + @statements.values.each do |stmt| + stmt.close + end + @statements.clear + end + def exec(sql, name = 'SQL', bind_values = []) log(sql, name) do result = nil - stmt = @connection.prepare(sql) + + if bind_values.empty? + stmt = @connection.prepare(sql) + else + stmt = @statements[sql] ||= @connection.prepare(sql) + end + stmt.execute(*bind_values.map { |col, val| col ? col.type_cast(val) : val }) @@ -326,7 +341,9 @@ module ActiveRecord stmt.each { |thing| values << thing } result = ActiveRecord::Result.new(cols, values) end - stmt.close + + stmt.close if bind_values.empty? + result end end -- cgit v1.2.3 From efc10a8a64d5748bc8355c0bd0373ca6b8c8755c Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 13 Oct 2010 12:00:28 -0700 Subject: eliminating some calls to execute() --- .../lib/active_record/connection_adapters/mysql_adapter.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 1c32f64d92..6236a906c3 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -307,10 +307,7 @@ module ActiveRecord def select_rows(sql, name = nil) @connection.query_with_result = true - result = execute(sql, name) - rows = [] - result.each { |row| rows << row } - result.free + rows = exec_without_stmt(sql, name).rows @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped rows end @@ -389,7 +386,7 @@ module ActiveRecord end def begin_db_transaction #:nodoc: - execute "BEGIN" + exec_without_stmt "BEGIN" rescue Exception # Transactions aren't supported end -- cgit v1.2.3 From ee959a9ca1f3eb75baa4e7dc6929cb209c610c85 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 13 Oct 2010 17:14:09 -0700 Subject: free the result after slurping --- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 6236a906c3..f2e8366bb9 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -336,6 +336,7 @@ module ActiveRecord cols = metadata.fetch_fields.map { |field| field.name } values = [] stmt.each { |thing| values << thing } + stmt.free_result result = ActiveRecord::Result.new(cols, values) end -- cgit v1.2.3 From fe2ee4fafdb6b29e4dde7ba287a4c342701e1056 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 13 Oct 2010 17:38:34 -0700 Subject: monkey patching Mysql::Stmt, calling free on the metadata --- .../lib/active_record/connection_adapters/mysql_adapter.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index f2e8366bb9..76d5611cd2 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -24,6 +24,12 @@ class Mysql end end +class Mysql + class Stmt + include Enumerable + end +end + module ActiveRecord class Base # Establishes a connection to the database that's used by all Active Record objects. @@ -334,12 +340,11 @@ module ActiveRecord }) if metadata = stmt.result_metadata cols = metadata.fetch_fields.map { |field| field.name } - values = [] - stmt.each { |thing| values << thing } - stmt.free_result - result = ActiveRecord::Result.new(cols, values) + metadata.free + result = ActiveRecord::Result.new(cols, stmt.to_a) end + stmt.free_result stmt.close if bind_values.empty? result -- cgit v1.2.3 From eb83eb6c98ae95a994dcac07d2c6246f7fd61962 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 13 Oct 2010 17:53:40 -0700 Subject: monkey patching with enumerable so we can call each --- .../lib/active_record/connection_adapters/mysql_adapter.rb | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 76d5611cd2..04dbcff95c 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -17,17 +17,12 @@ class Mysql class Time ### # This monkey patch is for test_additional_columns_from_join_table - def to_date Date.new(year, month, day) end end -end - -class Mysql - class Stmt - include Enumerable - end + class Stmt; include Enumerable end + class Result; include Enumerable end end module ActiveRecord @@ -357,10 +352,9 @@ module ActiveRecord log(sql, name) do result = @connection.query(sql) cols = result.fetch_fields.map { |field| field.name } - values = [] - result.each { |row| values << row } + rows = result.to_a result.free - ActiveRecord::Result.new(cols, values) + ActiveRecord::Result.new(cols, rows) end end -- cgit v1.2.3 From ffb999125a60cbdcee2e6709df019d55f21b22a6 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 15 Oct 2010 16:11:48 -0700 Subject: initial exec() method is working in pg adapter --- .../connection_adapters/postgresql_adapter.rb | 38 ++++++++++++++++++++++ .../adapters/postgresql/postgresql_adapter_test.rb | 17 ++++++++++ 2 files changed, 55 insertions(+) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 5949985e4d..75b4c1e653 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -220,11 +220,19 @@ module ActiveRecord @local_tz = nil @table_alias_length = nil @postgresql_version = nil + @statements = Hash.new { |h,k| h[k] = "a#{h.length + 1}" } connect @local_tz = execute('SHOW TIME ZONE').first["TimeZone"] end + def clear_cache! + @statements.each_value do |value| + exec "DEALLOCATE #{value}" + end + @statements.clear + end + # Is this connection alive and ready for queries? def active? if @connection.respond_to?(:status) @@ -250,8 +258,14 @@ module ActiveRecord end end + def reset! + clear_cache! + super + end + # Close the connection. def disconnect! + clear_cache! @connection.close rescue nil end @@ -501,6 +515,30 @@ module ActiveRecord end end + def exec(sql, name = 'SQL', binds = []) + return async_exec(sql, name, binds) if @async + + log(sql, name) do + end + end + + def async_exec(sql, name, binds) + log(sql, name) do + unless @statements.key? sql + @connection.prepare @statements[sql], sql + end + + key = @statements[sql] + + # Clear the queue + @connection.get_last_result + @connection.send_query_prepared(key, []) + @connection.block + result = @connection.get_last_result + ActiveRecord::Result.new(result.fields, result_as_array(result)) + end + end + # Executes an UPDATE query and returns the number of affected tuples. def update_sql(sql, name = nil) super.cmd_tuples diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 7b72151b57..a61482256a 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -5,6 +5,8 @@ module ActiveRecord class PostgreSQLAdapterTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection + @connection.exec('drop table if exists ex') + @connection.exec('create table ex(id serial primary key, data character varying(255))') end def test_table_alias_length @@ -12,6 +14,21 @@ module ActiveRecord @connection.table_alias_length end end + + def test_exec_no_binds + result = @connection.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 + + string = @connection.quote('foo') + @connection.exec("INSERT INTO ex (id, data) VALUES (1, #{string})") + result = @connection.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 end end end -- cgit v1.2.3 From e2813479f96e6dbfcfcde667797298611a9c9311 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 15 Oct 2010 16:15:58 -0700 Subject: basic bind parameters are working --- .../active_record/connection_adapters/postgresql_adapter.rb | 2 +- .../cases/adapters/postgresql/postgresql_adapter_test.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 75b4c1e653..80b987fb89 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -532,7 +532,7 @@ module ActiveRecord # Clear the queue @connection.get_last_result - @connection.send_query_prepared(key, []) + @connection.send_query_prepared(key, binds.map { |col, val| val }) @connection.block result = @connection.get_last_result ActiveRecord::Result.new(result.fields, result_as_array(result)) diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index a61482256a..0b295c9613 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -29,6 +29,18 @@ module ActiveRecord assert_equal [['1', 'foo']], result.rows end + + def test_exec_with_binds + string = @connection.quote('foo') + @connection.exec("INSERT INTO ex (id, data) VALUES (1, #{string})") + result = @connection.exec( + 'SELECT id, data FROM ex WHERE id = $1', 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 -- cgit v1.2.3 From 9d46e0d012e5c4687af4d14584f1230e71d7d654 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 15 Oct 2010 16:24:04 -0700 Subject: bind parameters are now typecast --- .../connection_adapters/postgresql_adapter.rb | 4 +++- .../cases/adapters/postgresql/postgresql_adapter_test.rb | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 80b987fb89..46c3486be6 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -532,7 +532,9 @@ module ActiveRecord # Clear the queue @connection.get_last_result - @connection.send_query_prepared(key, binds.map { |col, val| val }) + @connection.send_query_prepared(key, binds.map { |col, val| + col ? col.type_cast(val) : val + }) @connection.block result = @connection.get_last_result ActiveRecord::Result.new(result.fields, result_as_array(result)) diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 0b295c9613..70de8c5e6c 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -41,6 +41,20 @@ module ActiveRecord assert_equal [['1', 'foo']], result.rows end + + def test_exec_typecasts_bind_vals + string = @connection.quote('foo') + @connection.exec("INSERT INTO ex (id, data) VALUES (1, #{string})") + + column = @connection.columns('ex').find { |col| col.name == 'id' } + result = @connection.exec( + 'SELECT id, data FROM ex WHERE id = $1', nil, [[column, '1-fuu']]) + + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length + + assert_equal [['1', 'foo']], result.rows + end end end end -- cgit v1.2.3 From 43bbb25ddd413acc27998fe25be8f086585a7a2e Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 15 Oct 2010 16:41:44 -0700 Subject: bind substitution is working properly --- .../connection_adapters/postgresql_adapter.rb | 18 +++++++++++------- .../adapters/postgresql/postgresql_adapter_test.rb | 8 ++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 46c3486be6..71adfe8d8c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -228,7 +228,7 @@ module ActiveRecord def clear_cache! @statements.each_value do |value| - exec "DEALLOCATE #{value}" + @connection.query "DEALLOCATE #{value}" end @statements.clear end @@ -251,6 +251,7 @@ module ActiveRecord def reconnect! if @connection.respond_to?(:reset) @connection.reset + clear_cache! configure_connection else disconnect! @@ -515,6 +516,10 @@ module ActiveRecord end end + def substitute_for(column, current_values) + Arel.sql("$#{current_values.length + 1}") + end + def exec(sql, name = 'SQL', binds = []) return async_exec(sql, name, binds) if @async @@ -537,7 +542,9 @@ module ActiveRecord }) @connection.block result = @connection.get_last_result - ActiveRecord::Result.new(result.fields, result_as_array(result)) + ret = ActiveRecord::Result.new(result.fields, result_as_array(result)) + result.clear + return ret end end @@ -1014,11 +1021,8 @@ module ActiveRecord # Executes a SELECT query and returns the results, performing any data type # conversions that are required to be performed here instead of in PostgreSQLColumn. - def select(sql, name = nil) - fields, rows = select_raw(sql, name) - rows.map do |row| - Hash[fields.zip(row)] - end + def select(sql, name = nil, binds = []) + exec(sql, name, binds).to_a end def select_raw(sql, name = nil) diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 70de8c5e6c..b0fd2273df 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -55,6 +55,14 @@ module ActiveRecord assert_equal [['1', 'foo']], result.rows end + + def test_substitute_for + bind = @connection.substitute_for(nil, []) + assert_equal Arel.sql('$1'), bind + + bind = @connection.substitute_for(nil, [nil]) + assert_equal Arel.sql('$2'), bind + end end end end -- cgit v1.2.3 From 1b4e0b654210d024989dd4b46311807e2c823bdb Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 15 Oct 2010 18:43:41 -0700 Subject: prepare the statement before we cache the key --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 71adfe8d8c..3bd5acb23d 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -220,7 +220,7 @@ module ActiveRecord @local_tz = nil @table_alias_length = nil @postgresql_version = nil - @statements = Hash.new { |h,k| h[k] = "a#{h.length + 1}" } + @statements = {} connect @local_tz = execute('SHOW TIME ZONE').first["TimeZone"] @@ -530,7 +530,9 @@ module ActiveRecord def async_exec(sql, name, binds) log(sql, name) do unless @statements.key? sql - @connection.prepare @statements[sql], sql + nextkey = "a#{@statements.length + 1}" + @connection.prepare nextkey, sql + @statements[sql] = nextkey end key = @statements[sql] -- cgit v1.2.3 From 03e4ea5290ea4237474d9fd0c59e6bbb7e494fcb Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 15 Oct 2010 19:00:46 -0700 Subject: clear cache before resetting the connection --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 3bd5acb23d..4fd24ff857 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -250,8 +250,8 @@ module ActiveRecord # Close then reopen the connection. def reconnect! if @connection.respond_to?(:reset) - @connection.reset clear_cache! + @connection.reset configure_connection else disconnect! -- cgit v1.2.3 From 28a18b59885913682ffb1bceb87694eccd217df2 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 18 Oct 2010 09:44:23 -0700 Subject: folding async_exec to exec --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 7 ------- 1 file changed, 7 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 4fd24ff857..5e46f6994c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -521,13 +521,6 @@ module ActiveRecord end def exec(sql, name = 'SQL', binds = []) - return async_exec(sql, name, binds) if @async - - log(sql, name) do - end - end - - def async_exec(sql, name, binds) log(sql, name) do unless @statements.key? sql nextkey = "a#{@statements.length + 1}" -- cgit v1.2.3 From 1741bbe2d5cb58af76fb2ca31a25c05eadfadb71 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 21 Oct 2010 11:19:45 -0700 Subject: avoiding statement cache if there are no bind values --- .../active_record/connection_adapters/postgresql_adapter.rb | 11 +++++++++++ .../cases/adapters/postgresql/schema_authorization_test.rb | 12 ++++++++++++ 2 files changed, 23 insertions(+) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 5e46f6994c..64a07cccca 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -521,6 +521,8 @@ module ActiveRecord end def exec(sql, name = 'SQL', binds = []) + return exec_no_cache(sql, name) if binds.empty? + log(sql, name) do unless @statements.key? sql nextkey = "a#{@statements.length + 1}" @@ -962,6 +964,15 @@ module ActiveRecord end private + def exec_no_cache(sql, name) + log(sql, name) do + result = @connection.async_exec(sql) + ret = ActiveRecord::Result.new(result.fields, result_as_array(result)) + result.clear + ret + end + end + # The internal PostgreSQL identifier of the money data type. MONEY_COLUMN_TYPE_OID = 790 #:nodoc: # The internal PostgreSQL identifier of the BYTEA data type. diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb index 6f372edc38..8fdda20f77 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb @@ -43,6 +43,18 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase end end + def test_auth_with_bind + assert_nothing_raised do + set_session_auth + USERS.each do |u| + @connection.clear_cache! + set_session_auth u + assert_equal u, @connection.exec("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [[nil, 1]]).first['name'] + set_session_auth + end + end + end + def test_schema_uniqueness assert_nothing_raised do set_session_auth -- cgit v1.2.3 From 9d9aed433bd29eefb14807c02050cb8b88f15f0e Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 21 Oct 2010 11:26:16 -0700 Subject: add a session authorization setter to the pg connection --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 5 +++++ .../test/cases/adapters/postgresql/schema_authorization_test.rb | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 64a07cccca..e785cc6fea 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -389,6 +389,11 @@ module ActiveRecord end end + # Set the authorized user for this session + def session_auth=(user) + exec "SET SESSION AUTHORIZATION #{user}" + end + # REFERENTIAL INTEGRITY ==================================== def supports_disable_referential_integrity?() #:nodoc: diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb index 8fdda20f77..dc4f8fb955 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb @@ -43,6 +43,13 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase end end + def test_session_auth= + assert_raise(ActiveRecord::StatementInvalid) do + @connection.session_auth = 'DEFAULT' + @connection.execute "SELECT * FROM #{TABLE_NAME}" + end + end + def test_auth_with_bind assert_nothing_raised do set_session_auth @@ -90,7 +97,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase private def set_session_auth auth = nil - @connection.execute "SET SESSION AUTHORIZATION #{auth || 'default'}" + @connection.session_auth = auth || 'default' end end -- cgit v1.2.3 From 02128d628c4084eccd93c41db40f4d83db17f53c Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 21 Oct 2010 11:27:28 -0700 Subject: setting the authorized session clears the statement cache --- .../active_record/connection_adapters/postgresql_adapter.rb | 1 + .../cases/adapters/postgresql/schema_authorization_test.rb | 11 +++++++++++ 2 files changed, 12 insertions(+) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index e785cc6fea..4792f824cf 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -391,6 +391,7 @@ module ActiveRecord # Set the authorized user for this session def session_auth=(user) + clear_cache! exec "SET SESSION AUTHORIZATION #{user}" end diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb index dc4f8fb955..881631fb19 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb @@ -50,6 +50,17 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase end end + def test_setting_auth_clears_stmt_cache + assert_nothing_raised do + set_session_auth + USERS.each do |u| + set_session_auth u + assert_equal u, @connection.exec("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [[nil, 1]]).first['name'] + set_session_auth + end + end + end + def test_auth_with_bind assert_nothing_raised do set_session_auth -- cgit v1.2.3 From fca229e205e7d417a025198825cfe19c5c395bff Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 21 Oct 2010 14:26:42 -0700 Subject: caching column values --- .../active_record/connection_adapters/mysql_adapter.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 04dbcff95c..28413ce7a9 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -314,8 +314,8 @@ module ActiveRecord end def clear_cache! - @statements.values.each do |stmt| - stmt.close + @statements.values.each do |cache| + cache[:stmt].close end @statements.clear end @@ -324,17 +324,24 @@ module ActiveRecord log(sql, name) do result = nil + cache = {} if bind_values.empty? stmt = @connection.prepare(sql) else - stmt = @statements[sql] ||= @connection.prepare(sql) + cache = @statements[sql] ||= { + :stmt => @connection.prepare(sql) + } + stmt = cache[:stmt] end stmt.execute(*bind_values.map { |col, val| col ? col.type_cast(val) : val }) if metadata = stmt.result_metadata - cols = metadata.fetch_fields.map { |field| field.name } + cols = cache[:cols] ||= metadata.fetch_fields.map { |field| + field.name + } + metadata.free result = ActiveRecord::Result.new(cols, stmt.to_a) end -- cgit v1.2.3 From 104d0b263e5d9b17216f06c72d422d26ca5a537f Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 22 Oct 2010 11:29:27 -0700 Subject: adding backwards compatibility for non-prepare statement handling drivers --- .../abstract/database_statements.rb | 18 ++++++++++++++++-- .../active_record/connection_adapters/mysql_adapter.rb | 6 ++++++ .../connection_adapters/postgresql_adapter.rb | 6 ++++++ .../connection_adapters/sqlite_adapter.rb | 6 ++++++ 4 files changed, 34 insertions(+), 2 deletions(-) (limited to 'activerecord') 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 0e4f68e85c..16d01f051b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -4,7 +4,15 @@ module ActiveRecord # Returns an array of record hashes with the column names as keys and # column values as values. def select_all(sql, name = nil, bind_values = []) - select(sql, name, bind_values) + if supports_statement_cache? + select(sql, name, bind_values) + else + return select(sql, name) if bind_values.empty? + binds = bind_values.dup + select sql.gsub('?') { + quote(*binds.shift.reverse) + }, name + end end # Returns a record hash with the column names as keys and column values @@ -42,7 +50,7 @@ module ActiveRecord # Executes +sql+ statement in the context of this connection using # +bind_values+ as the bind substitutes. +name+ is logged along with # the executed +sql+ statement. - def exec(sql, name = nil, binds = []) + def exec(sql, name = 'SQL', binds = []) end # Returns the last auto-generated ID from the affected table. @@ -74,6 +82,12 @@ module ActiveRecord nil end + # Returns +true+ when the connection adapter supports prepared statement + # caching, otherwise returns +false+ + def supports_statement_cache? + false + end + # Runs the given block in a database transaction, and returns the result # of the block. # diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 28413ce7a9..36817fcbfd 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -203,6 +203,12 @@ module ActiveRecord ADAPTER_NAME end + # Returns +true+ when the connection adapter supports prepared statement + # caching, otherwise returns +false+ + def supports_statement_cache? + true + end + def supports_migrations? #:nodoc: true end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 4792f824cf..58c0f85dc2 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -211,6 +211,12 @@ module ActiveRecord ADAPTER_NAME end + # Returns +true+ when the connection adapter supports prepared statement + # caching, otherwise returns +false+ + def supports_statement_cache? + true + end + # Initializes and connects a PostgreSQL adapter. def initialize(connection, logger, connection_parameters, config) super(connection, logger) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 97f595af66..d95b950e18 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -62,6 +62,12 @@ module ActiveRecord sqlite_version >= '2.0.0' end + # Returns +true+ when the connection adapter supports prepared statement + # caching, otherwise returns +false+ + def supports_statement_cache? + true + end + def supports_migrations? #:nodoc: true end -- cgit v1.2.3 From e73b0b84d939af241dfbba8931d8add9724595e4 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 26 Oct 2010 13:02:14 -0700 Subject: renaming bind_values to binds where it makes sense --- activerecord/lib/active_record/base.rb | 4 ++-- .../connection_adapters/abstract/database_statements.rb | 12 ++++++------ .../active_record/connection_adapters/abstract_adapter.rb | 4 ++-- .../lib/active_record/connection_adapters/mysql_adapter.rb | 8 ++++---- .../lib/active_record/connection_adapters/sqlite_adapter.rb | 10 +++++----- 5 files changed, 19 insertions(+), 19 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 825b86f068..06a388cd21 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] # > [#"The Cheap Man Buys Twice"}>, ...] - def find_by_sql(sql, bind_values = []) - connection.select_all(sanitize_sql(sql), "#{name} Load", bind_values).collect! { |record| instantiate(record) } + def find_by_sql(sql, binds = []) + connection.select_all(sanitize_sql(sql), "#{name} Load", binds).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 16d01f051b..6178130c00 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -3,12 +3,12 @@ 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, bind_values = []) + def select_all(sql, name = nil, binds = []) if supports_statement_cache? - select(sql, name, bind_values) + select(sql, name, binds) else - return select(sql, name) if bind_values.empty? - binds = bind_values.dup + return select(sql, name) if binds.empty? + binds = binds.dup select sql.gsub('?') { quote(*binds.shift.reverse) }, name @@ -48,7 +48,7 @@ module ActiveRecord undef_method :execute # Executes +sql+ statement in the context of this connection using - # +bind_values+ as the bind substitutes. +name+ is logged along with + # +binds+ as the bind substitutes. +name+ is logged along with # the executed +sql+ statement. def exec(sql, name = 'SQL', binds = []) end @@ -274,7 +274,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, bind_values = []) + def select(sql, name = nil, binds = []) 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 6f014bf9f1..42d6f2bb21 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -99,8 +99,8 @@ module ActiveRecord end # Returns a bind substitution value given a +column+ and list of current - # +bind_values+ - def substitute_for(column, bind_values) + # +binds+ + def substitute_for(column, binds) Arel.sql '?' end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 36817fcbfd..7d47d06ae1 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -326,12 +326,12 @@ module ActiveRecord @statements.clear end - def exec(sql, name = 'SQL', bind_values = []) + def exec(sql, name = 'SQL', binds = []) log(sql, name) do result = nil cache = {} - if bind_values.empty? + if binds.empty? stmt = @connection.prepare(sql) else cache = @statements[sql] ||= { @@ -340,7 +340,7 @@ module ActiveRecord stmt = cache[:stmt] end - stmt.execute(*bind_values.map { |col, val| + stmt.execute(*binds.map { |col, val| col ? col.type_cast(val) : val }) if metadata = stmt.result_metadata @@ -353,7 +353,7 @@ module ActiveRecord end stmt.free_result - stmt.close if bind_values.empty? + stmt.close if binds.empty? result end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index d95b950e18..a4d7d12298 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -143,11 +143,11 @@ module ActiveRecord # DATABASE STATEMENTS ====================================== - def exec(sql, name = nil, bind_values = []) + def exec(sql, name = nil, binds = []) log(sql, name) do # Don't cache statements without bind values - if bind_values.empty? + if binds.empty? stmt = @connection.prepare(sql) cols = stmt.columns else @@ -157,7 +157,7 @@ module ActiveRecord stmt = cache[:stmt] cols = cache[:cols] ||= stmt.columns stmt.reset! - stmt.bind_params bind_values.map { |col, val| + stmt.bind_params binds.map { |col, val| col ? col.type_cast(val) : val } end @@ -313,8 +313,8 @@ module ActiveRecord end protected - def select(sql, name = nil, bind_values = []) #:nodoc: - exec(sql, name, bind_values).map do |row| + def select(sql, name = nil, binds = []) #:nodoc: + exec(sql, name, binds).map do |row| record = {} row.each do |key, value| record[key.sub(/^"?\w+"?\./, '')] = value if key.is_a?(String) -- cgit v1.2.3 From 7104122cc3fca4939d77a6780910cd98ff02fab0 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 27 Oct 2010 14:05:40 -0700 Subject: making query cache work with prepared statements --- .../connection_adapters/abstract/query_cache.rb | 13 +++++++------ activerecord/test/cases/query_cache_test.rb | 6 ++++++ 2 files changed, 13 insertions(+), 6 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 0ee61d0b6f..cbc09dfc3b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -49,23 +49,24 @@ module ActiveRecord @query_cache.clear end - def select_all(*args) + def select_all(sql, name = nil, binds = []) if @query_cache_enabled - cache_sql(args.first) { super } + cache_sql(sql, binds) { super } else super end end private - def cache_sql(sql) + def cache_sql(sql, binds) + key = [sql, binds] result = - if @query_cache.has_key?(sql) + if @query_cache.has_key?(key) ActiveSupport::Notifications.instrument("sql.active_record", :sql => sql, :name => "CACHE", :connection_id => self.object_id) - @query_cache[sql] + @query_cache[key] else - @query_cache[sql] = yield + @query_cache[key] = yield end if Array === result diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index bdd0cc6b7b..fe2aa1ccf6 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -22,6 +22,12 @@ class QueryCacheTest < ActiveRecord::TestCase end end + def test_find_queries_with_cache + Task.cache do + assert_queries(2) { Task.find(1); Task.find(1); Task.find(2) } + end + end + def test_count_queries_with_cache Task.cache do assert_queries(1) { Task.count; Task.count } -- cgit v1.2.3 From 9ce02118061fda778e592be83f7c4c3c7c75bfbf Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 27 Oct 2010 14:23:01 -0700 Subject: speeding up query cache --- .../lib/active_record/connection_adapters/abstract/query_cache.rb | 7 +++---- .../lib/active_record/connection_adapters/abstract_adapter.rb | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index cbc09dfc3b..6ec023f0e5 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -59,14 +59,13 @@ module ActiveRecord private def cache_sql(sql, binds) - key = [sql, binds] result = - if @query_cache.has_key?(key) + if @query_cache[sql].key?(binds) ActiveSupport::Notifications.instrument("sql.active_record", :sql => sql, :name => "CACHE", :connection_id => self.object_id) - @query_cache[key] + @query_cache[sql][binds] else - @query_cache[key] = yield + @query_cache[sql][binds] = yield end if Array === result diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 42d6f2bb21..f3fba9a3a9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -41,7 +41,7 @@ module ActiveRecord @active = nil @connection, @logger = connection, logger @query_cache_enabled = false - @query_cache = {} + @query_cache = Hash.new { |h,sql| h[sql] = {} } @instrumenter = ActiveSupport::Notifications.instrumenter end -- cgit v1.2.3 From f6ddb3553a38d7d479b0177ff29eb4a2a3c17e9f Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 27 Oct 2010 14:33:02 -0700 Subject: fisting test name --- activerecord/test/cases/query_cache_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index fe2aa1ccf6..5bb21a54bd 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -22,7 +22,7 @@ class QueryCacheTest < ActiveRecord::TestCase end end - def test_find_queries_with_cache + def test_find_queries_with_cache_multi_record Task.cache do assert_queries(2) { Task.find(1); Task.find(1); Task.find(2) } end -- cgit v1.2.3 From 6a3d6b7f1352efd3e7b931533740252b04850e27 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 27 Oct 2010 14:39:24 -0700 Subject: select_all() should always return an array, so no need to test --- .../active_record/connection_adapters/abstract/query_cache.rb | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 6ec023f0e5..d555308485 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/object/duplicable' - module ActiveRecord module ConnectionAdapters # :nodoc: module QueryCache @@ -68,13 +66,7 @@ module ActiveRecord @query_cache[sql][binds] = yield end - if Array === result - result.collect { |row| row.dup } - else - result.duplicable? ? result.dup : result - end - rescue TypeError - result + result.collect { |row| row.dup } end end end -- cgit v1.2.3