From afa148a4f0ac5e2a446b5fe87881a130e8a24f3d Mon Sep 17 00:00:00 2001 From: Cody Cutrer Date: Tue, 21 Jan 2014 16:56:53 -0700 Subject: create indexes inline in CREATE TABLE for MySQL This is important, because adding an index on a temporary table after it has been created would commit the transaction Conflicts: activerecord/CHANGELOG.md --- .../abstract/schema_statements.rb | 21 ++++++++++----------- .../connection_adapters/abstract_adapter.rb | 6 ++++++ .../connection_adapters/abstract_mysql_adapter.rb | 20 ++++++++++++++++++++ .../test/cases/adapters/mysql/active_schema_test.rb | 11 +++++++++++ .../cases/adapters/mysql2/active_schema_test.rb | 11 +++++++++++ 5 files changed, 58 insertions(+), 11 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 7f530ec5e4..59210c345f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -186,24 +186,23 @@ module ActiveRecord def create_table(table_name, options = {}) td = create_table_definition table_name, options[:temporary], options[:options], options[:as] - if !options[:as] - unless options[:id] == false - pk = options.fetch(:primary_key) { - Base.get_primary_key table_name.to_s.singularize - } - - td.primary_key pk, options.fetch(:id, :primary_key), options - end + unless options[:id] == false || options[:as] + pk = options.fetch(:primary_key) { + Base.get_primary_key table_name.to_s.singularize + } - yield td if block_given? + td.primary_key pk, options.fetch(:id, :primary_key), options end + yield td if block_given? + if options[:force] && table_exists?(table_name) drop_table(table_name, options) end - execute schema_creation.accept td - td.indexes.each_pair { |c,o| add_index table_name, c, o } + result = execute schema_creation.accept td + td.indexes.each_pair { |c,o| add_index table_name, c, o } unless supports_indexes_in_create? + result end # Creates a new join table with the name created using the lexical order of the first two diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index c7e5a18f02..543eeb5740 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -217,6 +217,12 @@ module ActiveRecord false end + # Does this adapter support creating indexes in the same statement as + # creating the table? As of this writing, only mysql does. + def supports_indexes_in_create? + false + end + # This is meant to be implemented by the adapters that support extensions def disable_extension(name) 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 9a819720f7..96c7d5d7eb 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -6,6 +6,17 @@ module ActiveRecord include Savepoints class SchemaCreation < AbstractAdapter::SchemaCreation + def visit_TableDefinition(o) + create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE " + create_sql << "#{quote_table_name(o.name)} " + statements = [] + statements.concat(o.columns.map { |c| accept c }) + statements.concat(o.indexes.map { |(column_name, options)| index_in_create(o.name, column_name, options) }) + create_sql << "(#{statements.join(', ')}) " + create_sql << "#{o.options}" + create_sql << " AS #{@conn.to_sql(o.as)}" if o.as + create_sql + end def visit_AddColumn(o) add_column_position!(super, column_options(o)) @@ -29,6 +40,11 @@ module ActiveRecord end sql end + + def index_in_create(table_name, column_name, options) + index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.send(:add_index_options, table_name, column_name, options) + "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}".gsub(' ', ' ').strip + end end def schema_creation @@ -225,6 +241,10 @@ module ActiveRecord version[0] >= 5 end + def supports_indexes_in_create? + true + end + def native_database_types NATIVE_DATABASE_TYPES end diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index e77138eb1d..072aa65000 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -116,6 +116,17 @@ class ActiveSchemaTest < ActiveRecord::TestCase end end + def test_indexes_in_create + begin + ActiveRecord::Base.connection.stubs(:index_name_exists?).returns(false) + expected = "CREATE TEMPORARY TABLE `temp` (INDEX `index_temp_on_zip` (`zip`)) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" + actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t| + t.index :zip + end + assert_equal expected, actual + end + end + private def with_real_execute ActiveRecord::Base.connection.singleton_class.class_eval do diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index 7905edaf83..91d880e5ae 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -116,6 +116,17 @@ class ActiveSchemaTest < ActiveRecord::TestCase end end + def test_indexes_in_create + begin + ActiveRecord::Base.connection.stubs(:index_name_exists?).returns(false) + expected = "CREATE TEMPORARY TABLE `temp` (INDEX `index_temp_on_zip` (`zip`)) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" + actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t| + t.index :zip + end + assert_equal expected, actual + end + end + private def with_real_execute ActiveRecord::Base.connection.singleton_class.class_eval do -- cgit v1.2.3 From 63c94efccb20c0b398dd90c0d209d6894eb22d92 Mon Sep 17 00:00:00 2001 From: Steve Rice Date: Tue, 25 Mar 2014 21:13:29 -0700 Subject: Fixes bugs for using indexes in CREATE TABLE by adding checks for table existence Also: - updates tests by stubbing table_exists? method - adds entry for creating indexes in CREATE TABLE to changelog --- activerecord/CHANGELOG.md | 15 +++++++++++++++ .../connection_adapters/abstract/schema_statements.rb | 2 +- .../connection_adapters/abstract_mysql_adapter.rb | 2 +- .../test/cases/adapters/mysql/active_schema_test.rb | 6 ++++-- .../test/cases/adapters/mysql2/active_schema_test.rb | 6 ++++-- 5 files changed, 25 insertions(+), 6 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 6e77493de9..ccd07ce248 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,18 @@ +* Create indexes inline in CREATE TABLE for MySQL + + This is important, because adding an index on a temporary table after it has been created + would commit the transaction. + It also allows creating and dropping indexed tables with fewer queries and fewer permissions required. + + Example: + + create_table :temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query" do |t| + t.index :zip + end + # => CREATE TEMPORARY TABLE temp (INDEX (zip)) AS SELECT id, name, zip FROM a_really_complicated_query + + *Cody Cutrer*, *Steve Rice* + * Save `has_one` association even if the record doesn't changed. Fixes #14407. diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 59210c345f..23404a5c48 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -795,7 +795,7 @@ module ActiveRecord if index_name.length > max_index_length raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters" end - if index_name_exists?(table_name, index_name, false) + if table_exists?(table_name) && index_name_exists?(table_name, index_name, false) raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists" end index_columns = quoted_columns_for_index(column_names, options).join(", ") 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 96c7d5d7eb..a1748c9261 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -12,7 +12,7 @@ module ActiveRecord statements = [] statements.concat(o.columns.map { |c| accept c }) statements.concat(o.indexes.map { |(column_name, options)| index_in_create(o.name, column_name, options) }) - create_sql << "(#{statements.join(', ')}) " + create_sql << "(#{statements.join(', ')}) " if statements.present? create_sql << "#{o.options}" create_sql << " AS #{@conn.to_sql(o.as)}" if o.as create_sql diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index 072aa65000..35681fb5ae 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -17,7 +17,8 @@ class ActiveSchemaTest < ActiveRecord::TestCase end def test_add_index - # add_index calls index_name_exists? which can't work since execute is stubbed + # add_index calls table_exists? and index_name_exists? which can't work since execute is stubbed + def (ActiveRecord::Base.connection).table_exists?(*); true; end def (ActiveRecord::Base.connection).index_name_exists?(*); false; end expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) " @@ -118,7 +119,8 @@ class ActiveSchemaTest < ActiveRecord::TestCase def test_indexes_in_create begin - ActiveRecord::Base.connection.stubs(:index_name_exists?).returns(false) + ActiveRecord::Base.connection.stubs(:table_exists?).with(:temp).returns(false) + ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false) expected = "CREATE TEMPORARY TABLE `temp` (INDEX `index_temp_on_zip` (`zip`)) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t| t.index :zip diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index 91d880e5ae..925b9c1bd1 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -17,7 +17,8 @@ class ActiveSchemaTest < ActiveRecord::TestCase end def test_add_index - # add_index calls index_name_exists? which can't work since execute is stubbed + # add_index calls table_exists? and index_name_exists? which can't work since execute is stubbed + def (ActiveRecord::Base.connection).table_exists?(*); true; end def (ActiveRecord::Base.connection).index_name_exists?(*); false; end expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) " @@ -118,7 +119,8 @@ class ActiveSchemaTest < ActiveRecord::TestCase def test_indexes_in_create begin - ActiveRecord::Base.connection.stubs(:index_name_exists?).returns(false) + ActiveRecord::Base.connection.stubs(:table_exists?).with(:temp).returns(false) + ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false) expected = "CREATE TEMPORARY TABLE `temp` (INDEX `index_temp_on_zip` (`zip`)) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t| t.index :zip -- cgit v1.2.3 From baf62e531686ee157746d239037be121f8191275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 26 Mar 2014 17:37:26 -0300 Subject: Invert the conditionals to make easier to read Also improve some of the code conventions --- .../connection_adapters/abstract/schema_statements.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 23404a5c48..b59d263dfa 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -186,10 +186,10 @@ module ActiveRecord def create_table(table_name, options = {}) td = create_table_definition table_name, options[:temporary], options[:options], options[:as] - unless options[:id] == false || options[:as] - pk = options.fetch(:primary_key) { + if options[:id] != false && !options[:as] + pk = options.fetch(:primary_key) do Base.get_primary_key table_name.to_s.singularize - } + end td.primary_key pk, options.fetch(:id, :primary_key), options end @@ -201,7 +201,7 @@ module ActiveRecord end result = execute schema_creation.accept td - td.indexes.each_pair { |c,o| add_index table_name, c, o } unless supports_indexes_in_create? + td.indexes.each_pair { |c, o| add_index(table_name, c, o) } unless supports_indexes_in_create? result end -- cgit v1.2.3 From fb743941d51475e416f4b4d1ecd8b3a91e501cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 26 Mar 2014 19:15:57 -0300 Subject: Remove unneeded comments about feature support on the adapters These comments will likely be outdated with time and doesn't include any information that can't be found in the adapters --- .../connection_adapters/abstract_adapter.rb | 29 +++++++--------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 543eeb5740..05e9b03e3a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -146,28 +146,24 @@ module ActiveRecord 'Abstract' end - # Does this adapter support migrations? Backend specific, as the - # abstract adapter always returns +false+. + # Does this adapter support migrations? def supports_migrations? false end # Can this adapter determine the primary key for tables not attached - # to an Active Record class, such as join tables? Backend specific, as - # the abstract adapter always returns +false+. + # to an Active Record class, such as join tables? def supports_primary_key? false end - # Does this adapter support using DISTINCT within COUNT? This is +true+ - # for all adapters except sqlite. + # Does this adapter support using DISTINCT within COUNT? def supports_count_distinct? true end # Does this adapter support DDL rollbacks in transactions? That is, would - # CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL, - # SQL Server, and others support this. MySQL and others do not. + # CREATE TABLE or ALTER TABLE get rolled back by a transaction? def supports_ddl_transactions? false end @@ -176,8 +172,7 @@ module ActiveRecord false end - # Does this adapter support savepoints? PostgreSQL and MySQL do, - # SQLite < 3.6.8 does not. + # Does this adapter support savepoints? def supports_savepoints? false end @@ -185,7 +180,6 @@ module ActiveRecord # Should primary key values be selected from their corresponding # sequence before the insert statement? If true, next_sequence_value # is called before each insert to set the record's primary key. - # This is false for all adapters but Firebird. def prefetch_primary_key?(table_name = nil) false end @@ -200,8 +194,7 @@ module ActiveRecord false end - # Does this adapter support explain? As of this writing sqlite3, - # mysql2, and postgresql are the only ones that do. + # Does this adapter support explain? def supports_explain? false end @@ -211,14 +204,13 @@ module ActiveRecord false end - # Does this adapter support database extensions? As of this writing only - # postgresql does. + # Does this adapter support database extensions? def supports_extensions? false end # Does this adapter support creating indexes in the same statement as - # creating the table? As of this writing, only mysql does. + # creating the table? def supports_indexes_in_create? false end @@ -231,14 +223,12 @@ module ActiveRecord def enable_extension(name) end - # A list of extensions, to be filled in by adapters that support them. At - # the moment only postgresql does. + # A list of extensions, to be filled in by adapters that support them. def extensions [] end # A list of index algorithms, to be filled by adapters that support them. - # MySQL and PostgreSQL have support for them right now. def index_algorithms {} end @@ -299,7 +289,6 @@ module ActiveRecord 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? false end -- cgit v1.2.3 From 4e2ca9b23fcaa98f4ef375a3a6d8eee579baec94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 26 Mar 2014 20:02:27 -0300 Subject: Improve the method * cache `o.name` value * Avoid extra `concat` call * Avoid extra `<<` call --- .../connection_adapters/abstract_mysql_adapter.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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 a1748c9261..35931d864f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -7,11 +7,12 @@ module ActiveRecord class SchemaCreation < AbstractAdapter::SchemaCreation def visit_TableDefinition(o) - create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE " - create_sql << "#{quote_table_name(o.name)} " - statements = [] - statements.concat(o.columns.map { |c| accept c }) - statements.concat(o.indexes.map { |(column_name, options)| index_in_create(o.name, column_name, options) }) + name = o.name + create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} " + + statements = o.columns.map { |c| accept c } + statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) }) + create_sql << "(#{statements.join(', ')}) " if statements.present? create_sql << "#{o.options}" create_sql << " AS #{@conn.to_sql(o.as)}" if o.as -- cgit v1.2.3 From e1a41fb9fc9eaedc3c220c8844aa04074746fa84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 26 Mar 2014 20:04:12 -0300 Subject: Make method private --- .../connection_adapters/abstract_mysql_adapter.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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 35931d864f..c74b916502 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -6,6 +6,12 @@ module ActiveRecord include Savepoints class SchemaCreation < AbstractAdapter::SchemaCreation + def visit_AddColumn(o) + add_column_position!(super, column_options(o)) + end + + private + def visit_TableDefinition(o) name = o.name create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} " @@ -19,11 +25,6 @@ module ActiveRecord create_sql end - def visit_AddColumn(o) - add_column_position!(super, column_options(o)) - end - - private def visit_ChangeColumnDefinition(o) column = o.column options = o.options -- cgit v1.2.3 From ee7db778afb7c867c4cf67777e19667431284975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 26 Mar 2014 20:04:55 -0300 Subject: Don't use send when we own the method --- .../abstract/schema_statements.rb | 68 +++++++++++----------- .../connection_adapters/abstract_mysql_adapter.rb | 2 +- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index b59d263dfa..aa99822389 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -739,6 +739,40 @@ module ActiveRecord Table.new(table_name, base) end + def add_index_options(table_name, column_name, options = {}) #:nodoc: + column_names = Array(column_name) + index_name = index_name(table_name, column: column_names) + + options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type) + + index_type = options[:unique] ? "UNIQUE" : "" + index_type = options[:type].to_s if options.key?(:type) + index_name = options[:name].to_s if options.key?(:name) + max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length + + if options.key?(:algorithm) + algorithm = index_algorithms.fetch(options[:algorithm]) { + raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}") + } + end + + using = "USING #{options[:using]}" if options[:using].present? + + if supports_partial_index? + index_options = options[:where] ? " WHERE #{options[:where]}" : "" + end + + if index_name.length > max_index_length + raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters" + end + if table_exists?(table_name) && index_name_exists?(table_name, index_name, false) + raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists" + end + index_columns = quoted_columns_for_index(column_names, options).join(", ") + + [index_name, index_type, index_columns, index_options, algorithm, using] + end + protected def add_index_sort_order(option_strings, column_names, options = {}) if options.is_a?(Hash) && order = options[:order] @@ -769,40 +803,6 @@ module ActiveRecord options.include?(:default) && !(options[:null] == false && options[:default].nil?) end - def add_index_options(table_name, column_name, options = {}) - column_names = Array(column_name) - index_name = index_name(table_name, column: column_names) - - options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type) - - index_type = options[:unique] ? "UNIQUE" : "" - index_type = options[:type].to_s if options.key?(:type) - index_name = options[:name].to_s if options.key?(:name) - max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length - - if options.key?(:algorithm) - algorithm = index_algorithms.fetch(options[:algorithm]) { - raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}") - } - end - - using = "USING #{options[:using]}" if options[:using].present? - - if supports_partial_index? - index_options = options[:where] ? " WHERE #{options[:where]}" : "" - end - - if index_name.length > max_index_length - raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters" - end - if table_exists?(table_name) && index_name_exists?(table_name, index_name, false) - raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists" - end - index_columns = quoted_columns_for_index(column_names, options).join(", ") - - [index_name, index_type, index_columns, index_options, algorithm, using] - end - def index_name_for_remove(table_name, options = {}) index_name = index_name(table_name, options) 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 c74b916502..b9fd75b610 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -44,7 +44,7 @@ module ActiveRecord end def index_in_create(table_name, column_name, options) - index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.send(:add_index_options, table_name, column_name, options) + index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options) "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}".gsub(' ', ' ').strip end end -- cgit v1.2.3 From 03d4ed3827da2086a8c8dadf4c25666248a39c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 26 Mar 2014 20:18:59 -0300 Subject: No need to gsub the string --- .../lib/active_record/connection_adapters/abstract_mysql_adapter.rb | 2 +- activerecord/test/cases/adapters/mysql/active_schema_test.rb | 2 +- activerecord/test/cases/adapters/mysql2/active_schema_test.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 b9fd75b610..e4c6502bb7 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -45,7 +45,7 @@ module ActiveRecord def index_in_create(table_name, column_name, options) index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options) - "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}".gsub(' ', ' ').strip + "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}" end end diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index 35681fb5ae..b27bd652f2 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -121,7 +121,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase begin ActiveRecord::Base.connection.stubs(:table_exists?).with(:temp).returns(false) ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false) - expected = "CREATE TEMPORARY TABLE `temp` (INDEX `index_temp_on_zip` (`zip`)) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" + expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`) ) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t| t.index :zip end diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index 925b9c1bd1..c15ecddd69 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -121,7 +121,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase begin ActiveRecord::Base.connection.stubs(:table_exists?).with(:temp).returns(false) ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false) - expected = "CREATE TEMPORARY TABLE `temp` (INDEX `index_temp_on_zip` (`zip`)) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" + expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`) ) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t| t.index :zip end -- cgit v1.2.3 From 8a37f3b59cb1399f71f02d9684fd9defcbd97697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 26 Mar 2014 20:30:28 -0300 Subject: No need to use begin/end blocks --- .../test/cases/adapters/mysql/active_schema_test.rb | 16 ++++++++-------- .../test/cases/adapters/mysql2/active_schema_test.rb | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index b27bd652f2..d1c644c016 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -118,15 +118,15 @@ class ActiveSchemaTest < ActiveRecord::TestCase end def test_indexes_in_create - begin - ActiveRecord::Base.connection.stubs(:table_exists?).with(:temp).returns(false) - ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false) - expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`) ) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" - actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t| - t.index :zip - end - assert_equal expected, actual + ActiveRecord::Base.connection.stubs(:table_exists?).with(:temp).returns(false) + ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false) + + expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`) ) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" + actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t| + t.index :zip end + + assert_equal expected, actual end private diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index c15ecddd69..81c614924f 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -118,15 +118,15 @@ class ActiveSchemaTest < ActiveRecord::TestCase end def test_indexes_in_create - begin - ActiveRecord::Base.connection.stubs(:table_exists?).with(:temp).returns(false) - ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false) - expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`) ) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" - actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t| - t.index :zip - end - assert_equal expected, actual + ActiveRecord::Base.connection.stubs(:table_exists?).with(:temp).returns(false) + ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false) + + expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`) ) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" + actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t| + t.index :zip end + + assert_equal expected, actual end private -- cgit v1.2.3 From cbe1bc29722ddeda12d8652c409cea156ddb85a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 26 Mar 2014 20:31:32 -0300 Subject: Improve CHANGELOG entry --- activerecord/CHANGELOG.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index ccd07ce248..42b7618fa7 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,8 +1,10 @@ -* Create indexes inline in CREATE TABLE for MySQL +* Create indexes inline in CREATE TABLE for MySQL. This is important, because adding an index on a temporary table after it has been created would commit the transaction. - It also allows creating and dropping indexed tables with fewer queries and fewer permissions required. + + It also allows creating and dropping indexed tables with fewer queries and fewer permissions + required. Example: @@ -11,7 +13,7 @@ end # => CREATE TEMPORARY TABLE temp (INDEX (zip)) AS SELECT id, name, zip FROM a_really_complicated_query - *Cody Cutrer*, *Steve Rice* + *Cody Cutrer*, *Steve Rice*, *Rafael Mendonça Franca* * Save `has_one` association even if the record doesn't changed. -- cgit v1.2.3