diff options
author | Rafael França <rafaelmfranca@gmail.com> | 2017-06-20 14:08:45 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-06-20 14:08:45 -0400 |
commit | d37af71886ded18885c4f71874d887fc5b638f2e (patch) | |
tree | 3b58d632fd5d2d7bee1492e0e244ed9e99aaf742 /activerecord | |
parent | 0f89fdcf254d972c5765bf3c1b0c8513ef9a9498 (diff) | |
parent | 4ee42379cc30a07a7d47b7be8c710dba0efa407a (diff) | |
download | rails-d37af71886ded18885c4f71874d887fc5b638f2e.tar.gz rails-d37af71886ded18885c4f71874d887fc5b638f2e.tar.bz2 rails-d37af71886ded18885c4f71874d887fc5b638f2e.zip |
Merge pull request #29504 from kirs/fixtures-arel-bulk
Use bulk INSERT to insert fixtures
Diffstat (limited to 'activerecord')
8 files changed, 116 insertions, 10 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index f75f1a9108..c65deffa8b 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,7 @@ +* Use bulk INSERT to insert fixtures for better performance. + + *Kir Shatrov* + * Prevent making bind param if casted value is nil. *Ryuta Kamizono* 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 56f0d6d1f4..dc766ce4fc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -296,6 +296,9 @@ module ActiveRecord # Inserts the given fixture into the table. Overridden in adapters that require # something beyond a simple insert (eg. Oracle). + # Most of adapters should implement `insert_fixtures` that leverages bulk SQL insert. + # We keep this method to provide fallback + # for databases like sqlite that do not support bulk inserts. def insert_fixture(fixture, table_name) fixture = fixture.stringify_keys @@ -312,12 +315,7 @@ module ActiveRecord table = Arel::Table.new(table_name) values = binds.map do |bind| - value = bind.value_for_database - begin - quote(value) - rescue TypeError - value = YAML.dump(value) - end + value = with_yaml_fallback(bind.value_for_database) [table[bind.name], value] end @@ -327,6 +325,40 @@ module ActiveRecord execute manager.to_sql, "Fixture Insert" end + # Inserts a set of fixtures into the table. Overridden in adapters that require + # something beyond a simple insert (eg. Oracle). + def insert_fixtures(fixtures, table_name) + return if fixtures.empty? + + columns = schema_cache.columns_hash(table_name) + + values = fixtures.map do |fixture| + fixture = fixture.stringify_keys + + unknown_columns = fixture.keys - columns.keys + if unknown_columns.any? + raise Fixture::FixtureError, %(table "#{table_name}" has no columns named #{unknown_columns.map(&:inspect).join(', ')}.) + end + + columns.map do |name, column| + if fixture.key?(name) + type = lookup_cast_type_from_column(column) + bind = Relation::QueryAttribute.new(name, fixture[name], type) + with_yaml_fallback(bind.value_for_database) + else + Arel.sql("DEFAULT") + end + end + end + + table = Arel::Table.new(table_name) + manager = Arel::InsertManager.new + manager.into(table) + columns.each_key { |column| manager.columns << table[column] } + manager.values = manager.create_values_list(values) + execute manager.to_sql, "Fixtures Insert" + end + def empty_insert_statement_value "DEFAULT VALUES" end @@ -388,6 +420,18 @@ module ActiveRecord end [relation, binds] end + + # Fixture value is quoted by Arel, however scalar values + # are not quotable. In this case we want to convert + # the column value to YAML. + def with_yaml_fallback(value) + begin + quote(value) + rescue TypeError + value = YAML.dump(value) + end + value + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 648c869915..c42e80ea2c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -526,8 +526,25 @@ module ActiveRecord index.using == :btree || super end + def insert_fixtures(*) + without_sql_mode("NO_AUTO_VALUE_ON_ZERO") { super } + end + private + def without_sql_mode(mode) + result = execute("SELECT @@SESSION.sql_mode") + current_mode = result.first[0] + return yield unless current_mode.include?(mode) + + sql_mode = "REPLACE(@@sql_mode, '#{mode}', '')" + execute("SET @@SESSION.sql_mode = #{sql_mode}") + yield + ensure + sql_mode = "CONCAT(@@sql_mode, ',#{mode}')" + execute("SET @@SESSION.sql_mode = #{sql_mode}") + end + def initialize_type_map(m) super diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 7233325d5a..ee2faf43b5 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -349,6 +349,12 @@ module ActiveRecord end end + def insert_fixtures(rows, table_name) + rows.each do |row| + insert_fixture(row, table_name) + end + end + private def table_structure(table_name) diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index a6b66c91e3..e9acb8acae 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -567,9 +567,7 @@ module ActiveRecord end table_rows.each do |fixture_set_name, rows| - rows.each do |row| - conn.insert_fixture(row, fixture_set_name) - end + conn.insert_fixtures(rows, fixture_set_name) end # Cap primary key sequences to max(pk). diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index c78c6178ff..121c62dadf 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -191,6 +191,12 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase assert_equal(PgArray.last.tags, tag_values) end + def test_insert_fixtures + tag_values = ["val1", "val2", "val3_with_'_multiple_quote_'_chars"] + @connection.insert_fixtures([{ "tags" => tag_values }], "pg_arrays") + assert_equal(PgArray.last.tags, tag_values) + end + def test_attribute_for_inspect_for_array_field record = PgArray.new { |a| a.ratings = (1..10).to_a } assert_equal("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]", record.attribute_for_inspect(:ratings)) diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index a0a6d3c7ef..b499e60922 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -54,6 +54,31 @@ class FixturesTest < ActiveRecord::TestCase end end + class InsertQuerySubscriber + attr_reader :events + + def initialize + @events = [] + end + + def call(_, _, _, _, values) + @events << values[:sql] if values[:sql] =~ /INSERT/ + end + end + + if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter) + def test_bulk_insert + begin + subscriber = InsertQuerySubscriber.new + subscription = ActiveSupport::Notifications.subscribe("sql.active_record", subscriber) + create_fixtures("bulbs") + assert_equal 1, subscriber.events.size, "It takes one INSERT query to insert two fixtures" + ensure + ActiveSupport::Notifications.unsubscribe(subscription) + end + end + end + def test_broken_yaml_exception badyaml = Tempfile.new ["foo", ".yml"] badyaml.write "a: : " @@ -248,7 +273,12 @@ class FixturesTest < ActiveRecord::TestCase e = assert_raise(ActiveRecord::Fixture::FixtureError) do ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/naked/yml", "parrots") end - assert_equal(%(table "parrots" has no column named "arrr".), e.message) + + if current_adapter?(:SQLite3Adapter) + assert_equal(%(table "parrots" has no column named "arrr".), e.message) + else + assert_equal(%(table "parrots" has no columns named "arrr", "foobar".), e.message) + end end def test_yaml_file_with_symbol_columns diff --git a/activerecord/test/fixtures/naked/yml/parrots.yml b/activerecord/test/fixtures/naked/yml/parrots.yml index 3e10331105..76f66e01ae 100644 --- a/activerecord/test/fixtures/naked/yml/parrots.yml +++ b/activerecord/test/fixtures/naked/yml/parrots.yml @@ -1,2 +1,3 @@ george: arrr: "Curious George" + foobar: Foobar |