mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Add support for if_not_exists
to indexes
When we try to create a table which already exists which also adds indexes, then the `if_not_exists` option passed to `create_table` is not extended to indexes. So the migration results into an error if the table and indexes already exist. This change extends the `if_not_exists` support to `add_index` so that if the migration tries to create a table which also has existing indexes, error won't be raised. Also as a side-effect individual `add_index` calls will also accept `if_not_exists` option and respect it henceforth. [Prathamesh Sonpatki, Justin George]
This commit is contained in:
parent
ddaa24ac70
commit
7ba08037f8
10 changed files with 88 additions and 10 deletions
|
@ -1,3 +1,20 @@
|
|||
* Add support for `if_not_exists` option for adding index.
|
||||
|
||||
The `add_index` method respects `if_not_exists` option. If it is set to true
|
||||
index won't be added.
|
||||
|
||||
Usage:
|
||||
|
||||
```
|
||||
add_index :users, :account_id, if_not_exists: true
|
||||
```
|
||||
|
||||
The `if_not_exists` option passed to `create_table` also gets propogated to indexes
|
||||
created within that migration so that if table and its indexes exist then there is no
|
||||
attempt to create them again.
|
||||
|
||||
*Prathamesh Sonpatki*
|
||||
|
||||
* Add `ActiveRecord::Base#previously_new_record?` to show if a record was new before the last save.
|
||||
|
||||
*Tom Ward*
|
||||
|
|
|
@ -317,7 +317,7 @@ module ActiveRecord
|
|||
|
||||
unless supports_indexes_in_create?
|
||||
td.indexes.each do |column_name, index_options|
|
||||
add_index(table_name, column_name, index_options)
|
||||
add_index(table_name, column_name, index_options.merge!(if_not_exists: td.if_not_exists))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -706,6 +706,16 @@ module ActiveRecord
|
|||
#
|
||||
# CREATE INDEX suppliers_name_index ON suppliers(name)
|
||||
#
|
||||
# ====== Creating a index which already exists
|
||||
#
|
||||
# add_index(:suppliers, :name, if_not_exists: true)
|
||||
#
|
||||
# generates:
|
||||
#
|
||||
# CREATE INDEX IF NOT EXISTS suppliers_name_index ON suppliers(name)
|
||||
#
|
||||
# Note: Not supported by MySQL.
|
||||
#
|
||||
# ====== Creating a unique index
|
||||
#
|
||||
# add_index(:accounts, [:branch_id, :party_id], unique: true)
|
||||
|
@ -805,8 +815,8 @@ module ActiveRecord
|
|||
#
|
||||
# For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
|
||||
def add_index(table_name, column_name, options = {})
|
||||
index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, **options)
|
||||
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
|
||||
index_name, index_type, index_columns, index_if_not_exists_clause, index_options = add_index_options(table_name, column_name, **options)
|
||||
execute "CREATE #{index_type} #{index_if_not_exists_clause} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
|
||||
end
|
||||
|
||||
# Removes the given index from the table.
|
||||
|
@ -1200,7 +1210,7 @@ module ActiveRecord
|
|||
def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc:
|
||||
column_names = index_column_names(column_name)
|
||||
|
||||
options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type, :opclass)
|
||||
options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type, :opclass, :if_not_exists)
|
||||
|
||||
index_type = options[:type].to_s if options.key?(:type)
|
||||
index_type ||= options[:unique] ? "UNIQUE" : ""
|
||||
|
@ -1219,11 +1229,13 @@ module ActiveRecord
|
|||
index_options = options[:where] ? " WHERE #{options[:where]}" : ""
|
||||
end
|
||||
|
||||
if_not_exists_index_clause = options[:if_not_exists] ? "INDEX IF NOT EXISTS" : "INDEX"
|
||||
|
||||
validate_index_length!(table_name, index_name, options.fetch(:internal, false))
|
||||
|
||||
index_columns = quoted_columns_for_index(column_names, **options).join(", ")
|
||||
|
||||
[index_name, index_type, index_columns, index_options, algorithm, using, comment]
|
||||
[index_name, index_type, index_columns, if_not_exists_index_clause, index_options, algorithm, using, comment]
|
||||
end
|
||||
|
||||
def options_include_default?(options)
|
||||
|
|
|
@ -360,7 +360,9 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def add_index(table_name, column_name, options = {}) #:nodoc:
|
||||
index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, **options)
|
||||
return if options[:if_not_exists] && index_exists?(table_name, column_name, options)
|
||||
|
||||
index_name, index_type, index_columns, _, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, **options)
|
||||
sql = +"CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
|
||||
execute add_sql_comment!(sql, comment)
|
||||
end
|
||||
|
@ -673,7 +675,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def add_index_for_alter(table_name, column_name, options = {})
|
||||
index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, **options)
|
||||
index_name, index_type, index_columns, _, _, index_algorithm, index_using = add_index_options(table_name, column_name, **options)
|
||||
index_algorithm[0, 0] = ", " if index_algorithm.present?
|
||||
"ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
|
||||
end
|
||||
|
|
|
@ -62,7 +62,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def index_in_create(table_name, column_name, options)
|
||||
index_name, index_type, index_columns, _, _, index_using, comment = @conn.add_index_options(table_name, column_name, **options)
|
||||
index_name, index_type, index_columns, _, _, _, index_using, comment = @conn.add_index_options(table_name, column_name, **options)
|
||||
add_sql_comment!((+"#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})"), comment)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -442,8 +442,8 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def add_index(table_name, column_name, options = {}) #:nodoc:
|
||||
index_name, index_type, index_columns_and_opclasses, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, **options)
|
||||
execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns_and_opclasses})#{index_options}").tap do
|
||||
index_name, index_type, index_columns_and_opclasses, index_if_not_exists_clause, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, **options)
|
||||
execute("CREATE #{index_type} #{index_if_not_exists_clause} #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns_and_opclasses})#{index_options}").tap do
|
||||
execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
|
||||
end
|
||||
end
|
||||
|
|
|
@ -61,6 +61,15 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
|
|||
expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) ALGORITHM = COPY"
|
||||
assert_equal expected, add_index(:people, :last_name, length: 10, using: :btree, algorithm: :copy)
|
||||
|
||||
with_real_execute do
|
||||
add_index(:people, :first_name)
|
||||
assert index_exists?(:people, :first_name)
|
||||
|
||||
assert_nothing_raised do
|
||||
add_index(:people, :first_name, if_not_exists: true)
|
||||
end
|
||||
end
|
||||
|
||||
assert_raise ArgumentError do
|
||||
add_index(:people, :last_name, algorithm: :coyp)
|
||||
end
|
||||
|
|
|
@ -67,6 +67,9 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase
|
|||
expected = %(CREATE INDEX "index_people_on_last_name" ON "people" ("last_name" NULLS FIRST))
|
||||
assert_equal expected, add_index(:people, :last_name, order: "NULLS FIRST")
|
||||
|
||||
expected = %(CREATE INDEX IF NOT EXISTS "index_people_on_last_name" ON "people" ("last_name"))
|
||||
assert_equal expected, add_index(:people, :last_name, if_not_exists: true)
|
||||
|
||||
assert_raise ArgumentError do
|
||||
add_index(:people, :last_name, algorithm: :copy)
|
||||
end
|
||||
|
|
|
@ -355,6 +355,16 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
def test_index_with_if_not_exists
|
||||
with_example_table do
|
||||
@conn.add_index "ex", "id"
|
||||
|
||||
assert_nothing_raised do
|
||||
@conn.add_index "ex", "id", if_not_exists: true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_non_unique_index
|
||||
with_example_table do
|
||||
@conn.add_index "ex", "id", name: "fun"
|
||||
|
|
|
@ -72,6 +72,14 @@ module ActiveRecord
|
|||
connection.add_index(table_name, "foo", name: good_index_name)
|
||||
end
|
||||
|
||||
def test_add_index_which_already_exists_does_not_raise_error
|
||||
connection.add_index(table_name, "foo", if_not_exists: true)
|
||||
|
||||
assert_nothing_raised do
|
||||
connection.add_index(table_name, "foo", if_not_exists: true)
|
||||
end
|
||||
end
|
||||
|
||||
def test_internal_index_with_name_matching_database_limit
|
||||
good_index_name = "x" * connection.index_name_length
|
||||
connection.add_index(table_name, "foo", name: good_index_name, internal: true)
|
||||
|
|
|
@ -155,6 +155,23 @@ class MigrationTest < ActiveRecord::TestCase
|
|||
connection.drop_table :testings, if_exists: true
|
||||
end
|
||||
|
||||
def test_create_table_with_indexes_and_if_not_exists_true
|
||||
connection = Person.connection
|
||||
connection.create_table :testings, force: true do |t|
|
||||
t.references :people
|
||||
t.string :foo
|
||||
end
|
||||
|
||||
assert_nothing_raised do
|
||||
connection.create_table :testings, if_not_exists: true do |t|
|
||||
t.references :people
|
||||
t.string :foo
|
||||
end
|
||||
end
|
||||
ensure
|
||||
connection.drop_table :testings, if_exists: true
|
||||
end
|
||||
|
||||
def test_create_table_with_force_true_does_not_drop_nonexisting_table
|
||||
# using a copy as we need the drop_table method to
|
||||
# continue to work for the ensure block of the test
|
||||
|
|
Loading…
Reference in a new issue