1
0
Fork 0
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:
Prathamesh Sonpatki 2020-02-22 23:17:29 +05:30 committed by eileencodes
parent ddaa24ac70
commit 7ba08037f8
No known key found for this signature in database
GPG key ID: BA5C575120BBE8DF
10 changed files with 88 additions and 10 deletions

View file

@ -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*

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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)

View file

@ -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