schema_statements.rb 49.3 KB
Newer Older
1 2 3
require "active_record/migration/join_table"
require "active_support/core_ext/string/access"
require "digest"
4

5 6 7
module ActiveRecord
  module ConnectionAdapters # :nodoc:
    module SchemaStatements
8 9
      include ActiveRecord::Migration::JoinTable

10
      # Returns a hash of mappings from the abstract data types to the native
11
      # database types. See TableDefinition#column for details on the recognized
12 13
      # abstract data types.
      def native_database_types
14 15 16
        {}
      end

17 18 19 20
      def table_options(table_name)
        nil
      end

21
      # Returns the table comment that's stored in database metadata.
22 23 24 25
      def table_comment(table_name)
        nil
      end

26
      # Truncates a table alias according to the limits of the current adapter.
27
      def table_alias_for(table_name)
28
        table_name[0...table_alias_length].tr(".", "_")
29 30
      end

31 32 33 34 35 36 37 38 39 40 41 42 43 44
      # Returns the relation names useable to back Active Record models.
      # For most adapters this means all #tables and #views.
      def data_sources
        tables | views
      end

      # Checks to see if the data source +name+ exists on the database.
      #
      #   data_source_exists?(:ebooks)
      #
      def data_source_exists?(name)
        data_sources.include?(name.to_s)
      end

45
      # Returns an array of table names defined in the database.
46
      def tables(name = nil)
47
        raise NotImplementedError, "#tables is not implemented"
48 49
      end

50 51 52
      # Checks to see if the table +table_name+ exists on the database.
      #
      #   table_exists?(:developers)
53
      #
54
      def table_exists?(table_name)
55
        tables.include?(table_name.to_s)
56 57
      end

58 59 60 61 62 63 64 65 66 67 68 69 70
      # Returns an array of view names defined in the database.
      def views
        raise NotImplementedError, "#views is not implemented"
      end

      # Checks to see if the view +view_name+ exists on the database.
      #
      #   view_exists?(:ebooks)
      #
      def view_exists?(view_name)
        views.include?(view_name.to_s)
      end

71 72 73
      # Returns an array of indexes for the given table.
      # def indexes(table_name, name = nil) end

S
Sebastian Martinez 已提交
74
      # Checks to see if an index exists on a table for a given index definition.
75
      #
76 77 78 79 80
      #   # Check an index exists
      #   index_exists?(:suppliers, :company_id)
      #
      #   # Check an index on multiple columns exists
      #   index_exists?(:suppliers, [:company_id, :company_type])
81
      #
82 83
      #   # Check a unique index exists
      #   index_exists?(:suppliers, :company_id, unique: true)
84
      #
85
      #   # Check an index with a custom name exists
86
      #   index_exists?(:suppliers, :company_id, name: "idx_company_id")
87 88
      #
      def index_exists?(table_name, column_name, options = {})
89 90 91 92
        column_names = Array(column_name).map(&:to_s)
        checks = []
        checks << lambda { |i| i.columns == column_names }
        checks << lambda { |i| i.unique } if options[:unique]
93
        checks << lambda { |i| i.name == options[:name].to_s } if options[:name]
94 95

        indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
96 97
      end

98 99
      # Returns an array of Column objects for the table specified by +table_name+.
      # See the concrete implementation for details on the expected parameter values.
100 101 102
      def columns(table_name)
        raise NotImplementedError, "#columns is not implemented"
      end
103

104 105
      # Checks to see if a column exists in a given table.
      #
106 107 108 109 110
      #   # Check a column exists
      #   column_exists?(:suppliers, :name)
      #
      #   # Check a column exists of a particular type
      #   column_exists?(:suppliers, :name, :string)
111
      #
112 113 114 115 116
      #   # Check a column exists with a specific definition
      #   column_exists?(:suppliers, :name, :string, limit: 100)
      #   column_exists?(:suppliers, :name, :string, default: 'default')
      #   column_exists?(:suppliers, :name, :string, null: false)
      #   column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2)
117
      #
118
      def column_exists?(table_name, column_name, type = nil, options = {})
119
        column_name = column_name.to_s
120 121 122
        checks = []
        checks << lambda { |c| c.name == column_name }
        checks << lambda { |c| c.type == type } if type
123
        (migration_keys - [:name]).each do |attr|
124 125 126 127
          checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr)
        end

        columns(table_name).any? { |c| checks.all? { |check| check[c] } }
128 129
      end

130 131
      # Returns just a table's primary key
      def primary_key(table_name)
132 133 134
        pk = primary_keys(table_name)
        pk = pk.first unless pk.size > 1
        pk
135 136
      end

P
Pratik Naik 已提交
137 138 139
      # Creates a new table with the name +table_name+. +table_name+ may either
      # be a String or a Symbol.
      #
140
      # There are two ways to work with #create_table. You can use the block
141 142 143
      # form or the regular form, like this:
      #
      # === Block form
P
Pratik Naik 已提交
144
      #
145 146 147 148 149 150 151 152
      #   # create_table() passes a TableDefinition object to the block.
      #   # This form will not only create the table, but also columns for the
      #   # table.
      #
      #   create_table(:suppliers) do |t|
      #     t.column :name, :string, limit: 60
      #     # Other fields here
      #   end
153
      #
P
Pratik Naik 已提交
154
      # === Block form, with shorthand
155 156 157 158 159 160
      #
      #   # You can also use the column types as method calls, rather than calling the column method.
      #   create_table(:suppliers) do |t|
      #     t.string :name, limit: 60
      #     # Other fields here
      #   end
P
Pratik Naik 已提交
161
      #
162
      # === Regular form
163 164 165 166 167
      #
      #   # Creates a table called 'suppliers' with no columns.
      #   create_table(:suppliers)
      #   # Add a column to 'suppliers'.
      #   add_column(:suppliers, :name, :string, {limit: 60})
168 169 170
      #
      # The +options+ hash can include the following keys:
      # [<tt>:id</tt>]
171
      #   Whether to automatically add a primary key column. Defaults to true.
172
      #   Join tables for {ActiveRecord::Base.has_and_belongs_to_many}[rdoc-ref:Associations::ClassMethods#has_and_belongs_to_many] should set it to false.
173 174
      #
      #   A Symbol can be used to specify the type of the generated primary key column.
175 176
      # [<tt>:primary_key</tt>]
      #   The name of the primary key, if one is to be added automatically.
177
      #   Defaults to +id+. If <tt>:id</tt> is false, then this option is ignored.
178
      #
179
      #   Note that Active Record models will automatically detect their
180 181
      #   primary key. This can be avoided by using
      #   {self.primary_key=}[rdoc-ref:AttributeMethods::PrimaryKey::ClassMethods#primary_key=] on the model
182
      #   to define the key explicitly.
183
      #
184 185 186 187
      # [<tt>:options</tt>]
      #   Any extra options you want appended to the table definition.
      # [<tt>:temporary</tt>]
      #   Make a temporary table.
188
      # [<tt>:force</tt>]
189
      #   Set to true to drop the table before creating it.
190
      #   Set to +:cascade+ to drop dependent objects as well.
191
      #   Defaults to false.
192
      # [<tt>:as</tt>]
193 194
      #   SQL to use to generate the table. When this option is used, the block is
      #   ignored, as are the <tt>:id</tt> and <tt>:primary_key</tt> options.
195 196
      #
      # ====== Add a backend specific option to the generated SQL (MySQL)
197 198 199
      #
      #   create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
      #
200
      # generates:
201 202
      #
      #   CREATE TABLE suppliers (
203
      #     id int auto_increment PRIMARY KEY
204
      #   ) ENGINE=InnoDB DEFAULT CHARSET=utf8
205 206
      #
      # ====== Rename the primary key column
207 208 209 210 211
      #
      #   create_table(:objects, primary_key: 'guid') do |t|
      #     t.column :name, :string, limit: 80
      #   end
      #
212
      # generates:
213 214
      #
      #   CREATE TABLE objects (
215
      #     guid int auto_increment PRIMARY KEY,
216 217
      #     name varchar(80)
      #   )
218
      #
219 220
      # ====== Change the primary key column type
      #
221 222
      #   create_table(:tags, id: :string) do |t|
      #     t.column :label, :string
223 224 225 226
      #   end
      #
      # generates:
      #
227
      #   CREATE TABLE tags (
228
      #     id varchar PRIMARY KEY,
229
      #     label varchar
230 231
      #   )
      #
232
      # ====== Do not add a primary key column
233 234 235 236 237 238
      #
      #   create_table(:categories_suppliers, id: false) do |t|
      #     t.column :category_id, :integer
      #     t.column :supplier_id, :integer
      #   end
      #
239
      # generates:
240 241 242 243 244
      #
      #   CREATE TABLE categories_suppliers (
      #     category_id int,
      #     supplier_id int
      #   )
245
      #
246 247 248 249 250 251 252 253 254 255
      # ====== Create a temporary table based on a query
      #
      #   create_table(:long_query, temporary: true,
      #     as: "SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id")
      #
      # generates:
      #
      #   CREATE TEMPORARY TABLE long_query AS
      #     SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
      #
256
      # See also TableDefinition#column for details on how to create columns.
257 258
      def create_table(table_name, comment: nil, **options)
        td = create_table_definition table_name, options[:temporary], options[:options], options[:as], comment: comment
259

260 261
        if options[:id] != false && !options[:as]
          pk = options.fetch(:primary_key) do
262
            Base.get_primary_key table_name.to_s.singularize
263
          end
264

265 266 267
          if pk.is_a?(Array)
            td.primary_keys pk
          else
268
            td.primary_key pk, options.fetch(:id, :primary_key), options
269
          end
270
        end
271

272 273
        yield td if block_given?

274
        if options[:force] && data_source_exists?(table_name)
275
          drop_table(table_name, options)
276 277
        end

278
        result = execute schema_creation.accept td
279 280

        unless supports_indexes_in_create?
281
          td.indexes.each do |column_name, index_options|
282 283 284 285
            add_index(table_name, column_name, index_options)
          end
        end

286
        if supports_comments? && !supports_comments_in_create?
R
Ryuta Kamizono 已提交
287
          change_table_comment(table_name, comment) if comment.present?
288

289
          td.columns.each do |column|
R
Ryuta Kamizono 已提交
290
            change_column_comment(table_name, column.name, column.comment) if column.comment.present?
291 292 293
          end
        end

294
        result
295
      end
296

297
      # Creates a new join table with the name created using the lexical order of the first two
W
Waseem Ahmad 已提交
298
      # arguments. These arguments can be a String or a Symbol.
299
      #
300 301
      #   # Creates a table called 'assemblies_parts' with no id.
      #   create_join_table(:assemblies, :parts)
302
      #
303
      # You can pass an +options+ hash which can include the following keys:
304
      # [<tt>:table_name</tt>]
305
      #   Sets the table name, overriding the default.
306 307 308 309 310 311 312 313 314 315
      # [<tt>:column_options</tt>]
      #   Any extra options you want appended to the columns definition.
      # [<tt>:options</tt>]
      #   Any extra options you want appended to the table definition.
      # [<tt>:temporary</tt>]
      #   Make a temporary table.
      # [<tt>:force</tt>]
      #   Set to true to drop the table before creating it.
      #   Defaults to false.
      #
316
      # Note that #create_join_table does not create any indices by default; you can use
317 318
      # its block form to do so yourself:
      #
319
      #   create_join_table :products, :categories do |t|
320 321
      #     t.index :product_id
      #     t.index :category_id
322
      #   end
323
      #
324
      # ====== Add a backend specific option to the generated SQL (MySQL)
325 326 327
      #
      #   create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
      #
328
      # generates:
329 330 331 332 333 334
      #
      #   CREATE TABLE assemblies_parts (
      #     assembly_id int NOT NULL,
      #     part_id int NOT NULL,
      #   ) ENGINE=InnoDB DEFAULT CHARSET=utf8
      #
335 336 337 338
      def create_join_table(table_1, table_2, options = {})
        join_table_name = find_join_table_name(table_1, table_2, options)

        column_options = options.delete(:column_options) || {}
339
        column_options.reverse_merge!(null: false)
340
        type = column_options.delete(:type) || :integer
341

342
        t1_column, t2_column = [table_1, table_2].map { |t| t.to_s.singularize.foreign_key }
343

344
        create_table(join_table_name, options.merge!(id: false)) do |td|
345 346
          td.send type, t1_column, column_options
          td.send type, t2_column, column_options
347
          yield td if block_given?
348 349 350
        end
      end

M
Marc-Andre Lafortune 已提交
351
      # Drops the join table specified by the given arguments.
352
      # See #create_join_table for details.
M
Marc-Andre Lafortune 已提交
353 354 355
      #
      # Although this command ignores the block if one is given, it can be helpful
      # to provide one in a migration's +change+ method so it can be reverted.
356
      # In that case, the block will be used by #create_join_table.
M
Marc-Andre Lafortune 已提交
357 358 359 360 361
      def drop_join_table(table_1, table_2, options = {})
        join_table_name = find_join_table_name(table_1, table_2, options)
        drop_table(join_table_name)
      end

362 363
      # A block for changing columns in +table+.
      #
364 365 366 367 368
      #   # change_table() yields a Table instance
      #   change_table(:suppliers) do |t|
      #     t.column :name, :string, limit: 60
      #     # Other column alterations here
      #   end
369
      #
370 371 372
      # The +options+ hash can include the following keys:
      # [<tt>:bulk</tt>]
      #   Set this to true to make this a bulk alter query, such as
373
      #
374
      #     ALTER TABLE `users` ADD COLUMN age INT, ADD COLUMN birthdate DATETIME ...
375 376 377
      #
      #   Defaults to false.
      #
378
      # ====== Add a column
379 380 381 382
      #
      #   change_table(:suppliers) do |t|
      #     t.column :name, :string, limit: 60
      #   end
383 384
      #
      # ====== Add 2 integer columns
385 386 387 388
      #
      #   change_table(:suppliers) do |t|
      #     t.integer :width, :height, null: false, default: 0
      #   end
389 390
      #
      # ====== Add created_at/updated_at columns
391 392 393 394
      #
      #   change_table(:suppliers) do |t|
      #     t.timestamps
      #   end
395 396 397
      #
      # ====== Add a foreign key column
      #
398 399 400 401 402
      #   change_table(:suppliers) do |t|
      #     t.references :company
      #   end
      #
      # Creates a <tt>company_id(integer)</tt> column.
403 404
      #
      # ====== Add a polymorphic foreign key column
405
      #
406
      #  change_table(:suppliers) do |t|
A
AvnerCohen 已提交
407
      #    t.belongs_to :company, polymorphic: true
408 409
      #  end
      #
410
      # Creates <tt>company_type(varchar)</tt> and <tt>company_id(integer)</tt> columns.
411 412
      #
      # ====== Remove a column
413
      #
414 415 416 417
      #  change_table(:suppliers) do |t|
      #    t.remove :company
      #  end
      #
418
      # ====== Remove several columns
419
      #
420 421 422 423 424 425
      #  change_table(:suppliers) do |t|
      #    t.remove :company_id
      #    t.remove :width, :height
      #  end
      #
      # ====== Remove an index
426
      #
427 428 429 430
      #  change_table(:suppliers) do |t|
      #    t.remove_index :company_id
      #  end
      #
431
      # See also Table for details on all of the various column transformations.
432 433 434
      def change_table(table_name, options = {})
        if supports_bulk_alter? && options[:bulk]
          recorder = ActiveRecord::Migration::CommandRecorder.new(self)
435
          yield update_table_definition(table_name, recorder)
436 437
          bulk_change_table(table_name, recorder.commands)
        else
438
          yield update_table_definition(table_name, self)
439
        end
440
      end
441

442
      # Renames a table.
443
      #
444 445
      #   rename_table('octopuses', 'octopi')
      #
446
      def rename_table(table_name, new_name)
447 448
        raise NotImplementedError, "rename_table is not implemented"
      end
449

450
      # Drops a table from the database.
451
      #
452 453 454
      # [<tt>:force</tt>]
      #   Set to +:cascade+ to drop dependent objects as well.
      #   Defaults to false.
455 456 457
      # [<tt>:if_exists</tt>]
      #   Set to +true+ to only drop the table if it exists.
      #   Defaults to false.
458 459 460
      #
      # Although this command ignores most +options+ and the block if one is given,
      # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
461
      # In that case, +options+ and the block will be used by #create_table.
462
      def drop_table(table_name, options = {})
463
        execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
464 465
      end

466 467 468 469 470
      # Add a new +type+ column named +column_name+ to +table_name+.
      #
      # The +type+ parameter is normally one of the migrations native types,
      # which is one of the following:
      # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
471
      # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:numeric</tt>,
472 473 474 475 476 477 478 479 480
      # <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>,
      # <tt>:binary</tt>, <tt>:boolean</tt>.
      #
      # You may use a type not in this list as long as it is supported by your
      # database (for example, "polygon" in MySQL), but this will not be database
      # agnostic and should usually be avoided.
      #
      # Available options are (none of these exists by default):
      # * <tt>:limit</tt> -
481
      #   Requests a maximum column length. This is the number of characters for a <tt>:string</tt> column
482 483
      #   and number of bytes for <tt>:text</tt>, <tt>:binary</tt> and <tt>:integer</tt> columns.
      # * <tt>:default</tt> -
484
      #   The column's default value. Use +nil+ for +NULL+.
485 486 487 488
      # * <tt>:null</tt> -
      #   Allows or disallows +NULL+ values in the column. This option could
      #   have been named <tt>:null_allowed</tt>.
      # * <tt>:precision</tt> -
489
      #   Specifies the precision for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
490
      # * <tt>:scale</tt> -
491
      #   Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
492
      #
493
      # Note: The precision is the total number of significant digits,
494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
      # and the scale is the number of digits that can be stored following
      # the decimal point. For example, the number 123.45 has a precision of 5
      # and a scale of 2. A decimal with a precision of 5 and a scale of 2 can
      # range from -999.99 to 999.99.
      #
      # Please be aware of different RDBMS implementations behavior with
      # <tt>:decimal</tt> columns:
      # * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
      #   <tt>:precision</tt>, and makes no comments about the requirements of
      #   <tt>:precision</tt>.
      # * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
      #   Default is (10,0).
      # * PostgreSQL: <tt>:precision</tt> [1..infinity],
      #   <tt>:scale</tt> [0..infinity]. No default.
      # * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
      #   but the maximum supported <tt>:precision</tt> is 16. No default.
      # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
      #   Default is (38,0).
      # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
      #   Default unknown.
514
      # * SqlServer: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535
      #   Default (38,0).
      #
      # == Examples
      #
      #  add_column(:users, :picture, :binary, limit: 2.megabytes)
      #  # ALTER TABLE "users" ADD "picture" blob(2097152)
      #
      #  add_column(:articles, :status, :string, limit: 20, default: 'draft', null: false)
      #  # ALTER TABLE "articles" ADD "status" varchar(20) DEFAULT 'draft' NOT NULL
      #
      #  add_column(:answers, :bill_gates_money, :decimal, precision: 15, scale: 2)
      #  # ALTER TABLE "answers" ADD "bill_gates_money" decimal(15,2)
      #
      #  add_column(:measurements, :sensor_reading, :decimal, precision: 30, scale: 20)
      #  # ALTER TABLE "measurements" ADD "sensor_reading" decimal(30,20)
      #
      #  # While :scale defaults to zero on most databases, it
      #  # probably wouldn't hurt to include it.
      #  add_column(:measurements, :huge_integer, :decimal, precision: 30)
      #  # ALTER TABLE "measurements" ADD "huge_integer" decimal(30)
      #
O
oss92 已提交
536 537 538 539
      #  # Defines a column that stores an array of a type.
      #  add_column(:users, :skills, :text, array: true)
      #  # ALTER TABLE "users" ADD "skills" text[]
      #
540 541 542
      #  # Defines a column with a database-specific type.
      #  add_column(:shapes, :triangle, 'polygon')
      #  # ALTER TABLE "shapes" ADD "triangle" polygon
543
      def add_column(table_name, column_name, type, options = {})
544 545 546
        at = create_alter_table table_name
        at.add_column(column_name, type, options)
        execute schema_creation.accept at
547 548
      end

549
      # Removes the given columns from the table definition.
550
      #
551 552
      #   remove_columns(:suppliers, :qualification, :experience)
      #
553 554 555 556 557 558 559 560 561
      def remove_columns(table_name, *column_names)
        raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") if column_names.empty?
        column_names.each do |column_name|
          remove_column(table_name, column_name)
        end
      end

      # Removes the column from the table definition.
      #
562
      #   remove_column(:suppliers, :qualification)
563 564 565
      #
      # The +type+ and +options+ parameters will be ignored if present. It can be helpful
      # to provide these in a migration's +change+ method so it can be reverted.
566
      # In that case, +type+ and +options+ will be used by #add_column.
567
      def remove_column(table_name, column_name, type = nil, options = {})
568
        execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
569
      end
570

571 572
      # Changes the column's definition according to the new options.
      # See TableDefinition#column for details of the options you can use.
573
      #
574 575 576
      #   change_column(:suppliers, :name, :string, limit: 80)
      #   change_column(:accounts, :description, :text)
      #
577 578 579 580
      def change_column(table_name, column_name, type, options = {})
        raise NotImplementedError, "change_column is not implemented"
      end

581 582 583 584 585 586
      # Sets a new default value for a column:
      #
      #   change_column_default(:suppliers, :qualification, 'new')
      #   change_column_default(:accounts, :authorized, 1)
      #
      # Setting the default to +nil+ effectively drops the default:
587
      #
588
      #   change_column_default(:users, :email, nil)
589
      #
590 591 592 593 594 595
      # Passing a hash containing +:from+ and +:to+ will make this change
      # reversible in migration:
      #
      #   change_column_default(:posts, :state, from: nil, to: "draft")
      #
      def change_column_default(table_name, column_name, default_or_changes)
596 597 598
        raise NotImplementedError, "change_column_default is not implemented"
      end

599
      # Sets or removes a <tt>NOT NULL</tt> constraint on a column. The +null+ flag
C
Carlos Antonio da Silva 已提交
600
      # indicates whether the value can be +NULL+. For example
601 602 603 604 605 606 607 608 609 610
      #
      #   change_column_null(:users, :nickname, false)
      #
      # says nicknames cannot be +NULL+ (adds the constraint), whereas
      #
      #   change_column_null(:users, :nickname, true)
      #
      # allows them to be +NULL+ (drops the constraint).
      #
      # The method accepts an optional fourth argument to replace existing
611
      # <tt>NULL</tt>s with some other value. Use that one when enabling the
612 613 614 615 616 617 618
      # constraint if needed, since otherwise those rows would not be valid.
      #
      # Please note the fourth argument does not set a column's default.
      def change_column_null(table_name, column_name, null, default = nil)
        raise NotImplementedError, "change_column_null is not implemented"
      end

619
      # Renames a column.
620
      #
621 622
      #   rename_column(:suppliers, :description, :name)
      #
623 624 625 626
      def rename_column(table_name, column_name, new_column_name)
        raise NotImplementedError, "rename_column is not implemented"
      end

627
      # Adds a new index to the table. +column_name+ can be a single Symbol, or
628 629
      # an Array of Symbols.
      #
630 631
      # The index will be named after the table and the column name(s), unless
      # you pass <tt>:name</tt> as an option.
632
      #
633
      # ====== Creating a simple index
634 635 636
      #
      #   add_index(:suppliers, :name)
      #
637
      # generates:
638 639
      #
      #   CREATE INDEX suppliers_name_index ON suppliers(name)
640
      #
641
      # ====== Creating a unique index
642 643 644
      #
      #   add_index(:accounts, [:branch_id, :party_id], unique: true)
      #
645
      # generates:
646 647
      #
      #   CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
648
      #
649
      # ====== Creating a named index
650 651 652
      #
      #   add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party')
      #
653
      # generates:
654
      #
655
      #  CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
656 657
      #
      # ====== Creating an index with specific key length
658 659 660
      #
      #   add_index(:accounts, :name, name: 'by_name', length: 10)
      #
661
      # generates:
662
      #
663 664
      #   CREATE INDEX by_name ON accounts(name(10))
      #
665 666
      # ====== Creating an index with specific key lengths for multiple keys
      #
667 668
      #   add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
      #
669
      # generates:
670
      #
671 672 673
      #   CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
      #
      # Note: SQLite doesn't support index length.
674 675
      #
      # ====== Creating an index with a sort order (desc or asc, asc is the default)
676 677 678
      #
      #   add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
      #
679
      # generates:
680
      #
681 682 683
      #   CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
      #
      # Note: MySQL doesn't yet support index order (it accepts the syntax but ignores it).
684
      #
685
      # ====== Creating a partial index
686 687 688
      #
      #   add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active")
      #
689
      # generates:
690
      #
691
      #   CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
692
      #
693 694
      # Note: Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+.
      #
D
doabit 已提交
695
      # ====== Creating an index with a specific method
696
      #
697
      #   add_index(:developers, :name, using: 'btree')
698
      #
699
      # generates:
700
      #
701 702
      #   CREATE INDEX index_developers_on_name ON developers USING btree (name) -- PostgreSQL
      #   CREATE INDEX index_developers_on_name USING btree ON developers (name) -- MySQL
D
doabit 已提交
703 704 705
      #
      # Note: only supported by PostgreSQL and MySQL
      #
706 707
      # ====== Creating an index with a specific type
      #
708
      #   add_index(:developers, :name, type: :fulltext)
709
      #
710
      # generates:
711 712 713
      #
      #   CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
      #
714
      # Note: only supported by MySQL.
715
      def add_index(table_name, column_name, options = {})
716 717
        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}"
718 719
      end

720 721
      # Removes the given index from the table.
      #
722
      # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
723
      #
724
      #   remove_index :accounts, :branch_id
725
      #
726
      # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
727
      #
A
AvnerCohen 已提交
728
      #   remove_index :accounts, column: :branch_id
729
      #
730
      # Removes the index on +branch_id+ and +party_id+ in the +accounts+ table if exactly one such index exists.
731
      #
A
AvnerCohen 已提交
732
      #   remove_index :accounts, column: [:branch_id, :party_id]
733 734 735
      #
      # Removes the index named +by_branch_party+ in the +accounts+ table.
      #
A
AvnerCohen 已提交
736
      #   remove_index :accounts, name: :by_branch_party
737
      #
738
      def remove_index(table_name, options = {})
739
        index_name = index_name_for_remove(table_name, options)
740
        execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
741 742
      end

743 744 745
      # Renames an index.
      #
      # Rename the +index_people_on_last_name+ index to +index_users_on_last_name+:
746 747
      #
      #   rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
748
      #
749
      def rename_index(table_name, old_name, new_name)
750 751
        validate_index_length!(table_name, new_name)

752 753 754
        # this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
        old_index_def = indexes(table_name).detect { |i| i.name == old_name }
        return unless old_index_def
755 756
        add_index(table_name, old_index_def.columns, name: new_name, unique: old_index_def.unique)
        remove_index(table_name, name: old_name)
757 758 759
      end

      def index_name(table_name, options) #:nodoc:
R
Rafael Mendonça França 已提交
760
        if Hash === options
761
          if options[:column]
762
            "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
763
          elsif options[:name]
764
            options[:name]
765 766 767 768
          else
            raise ArgumentError, "You must specify the index name"
          end
        else
769
          index_name(table_name, column: options)
770 771 772
        end
      end

773
      # Verifies the existence of an index with a given name.
774 775 776
      #
      # The default argument is returned if the underlying implementation does not define the indexes method,
      # as there's no way to determine the correct answer in that case.
777
      def index_name_exists?(table_name, index_name, default)
778
        return default unless respond_to?(:indexes)
779 780
        index_name = index_name.to_s
        indexes(table_name).detect { |i| i.name == index_name }
781 782
      end

783 784 785
      # Adds a reference. The reference column is an integer by default,
      # the <tt>:type</tt> option can be used to specify a different type.
      # Optionally adds a +_type+ column, if <tt>:polymorphic</tt> option is provided.
786
      # #add_reference and #add_belongs_to are acceptable.
787
      #
788 789 790 791
      # The +options+ hash can include the following keys:
      # [<tt>:type</tt>]
      #   The reference column type. Defaults to +:integer+.
      # [<tt>:index</tt>]
792
      #   Add an appropriate index. Defaults to true.
793
      #   See #add_index for usage of this option.
794
      # [<tt>:foreign_key</tt>]
795
      #   Add an appropriate foreign key constraint. Defaults to false.
796
      # [<tt>:polymorphic</tt>]
797
      #   Whether an additional +_type+ column should be added. Defaults to false.
798 799
      # [<tt>:null</tt>]
      #   Whether the column allows nulls. Defaults to true.
800
      #
801
      # ====== Create a user_id integer column
802 803
      #
      #   add_reference(:products, :user)
804
      #
805 806 807 808
      # ====== Create a user_id string column
      #
      #   add_reference(:products, :user, type: :string)
      #
809
      # ====== Create supplier_id, supplier_type columns and appropriate index
810 811
      #
      #   add_reference(:products, :supplier, polymorphic: true, index: true)
812
      #
813 814 815 816 817 818 819 820
      # ====== Create a supplier_id column with a unique index
      #
      #   add_reference(:products, :supplier, index: { unique: true })
      #
      # ====== Create a supplier_id column with a named index
      #
      #   add_reference(:products, :supplier, index: { name: "my_supplier_index" })
      #
821 822 823 824
      # ====== Create a supplier_id column and appropriate foreign key
      #
      #   add_reference(:products, :supplier, foreign_key: true)
      #
825 826 827 828
      # ====== Create a supplier_id column and a foreign key to the firms table
      #
      #   add_reference(:products, :supplier, foreign_key: {to_table: :firms})
      #
829 830
      def add_reference(table_name, *args)
        ReferenceDefinition.new(*args).add_to(update_table_definition(table_name, self))
831 832 833 834
      end
      alias :add_belongs_to :add_reference

      # Removes the reference(s). Also removes a +type+ column if one exists.
835
      # #remove_reference and #remove_belongs_to are acceptable.
836 837
      #
      # ====== Remove the reference
838 839
      #
      #   remove_reference(:products, :user, index: true)
840 841
      #
      # ====== Remove polymorphic reference
842 843
      #
      #   remove_reference(:products, :supplier, polymorphic: true)
844
      #
845 846 847 848
      # ====== Remove the reference with a foreign key
      #
      #   remove_reference(:products, :user, index: true, foreign_key: true)
      #
849 850
      def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options)
        if foreign_key
851
          reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
852 853 854 855 856 857
          if foreign_key.is_a?(Hash)
            foreign_key_options = foreign_key
          else
            foreign_key_options = { to_table: reference_name }
          end
          remove_foreign_key(table_name, **foreign_key_options)
858
        end
859

860
        remove_column(table_name, "#{ref_name}_id")
861
        remove_column(table_name, "#{ref_name}_type") if polymorphic
862 863 864
      end
      alias :remove_belongs_to :remove_reference

Y
Yves Senn 已提交
865
      # Returns an array of foreign keys for the given table.
866
      # The foreign keys are represented as ForeignKeyDefinition objects.
867 868 869 870
      def foreign_keys(table_name)
        raise NotImplementedError, "foreign_keys is not implemented"
      end

Y
Yves Senn 已提交
871 872 873 874
      # Adds a new foreign key. +from_table+ is the table with the key column,
      # +to_table+ contains the referenced primary key.
      #
      # The foreign key will be named after the following pattern: <tt>fk_rails_<identifier></tt>.
875 876
      # +identifier+ is a 10 character long string which is deterministically generated from the
      # +from_table+ and +column+. A custom name can be specified with the <tt>:name</tt> option.
Y
Yves Senn 已提交
877 878 879 880 881 882 883
      #
      # ====== Creating a simple foreign key
      #
      #   add_foreign_key :articles, :authors
      #
      # generates:
      #
884
      #   ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
Y
Yves Senn 已提交
885 886 887 888 889 890 891 892 893 894 895 896 897 898 899
      #
      # ====== Creating a foreign key on a specific column
      #
      #   add_foreign_key :articles, :users, column: :author_id, primary_key: "lng_id"
      #
      # generates:
      #
      #   ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id")
      #
      # ====== Creating a cascading foreign key
      #
      #   add_foreign_key :articles, :authors, on_delete: :cascade
      #
      # generates:
      #
900
      #   ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
Y
Yves Senn 已提交
901 902 903 904 905 906 907 908 909
      #
      # The +options+ hash can include the following keys:
      # [<tt>:column</tt>]
      #   The foreign key column name on +from_table+. Defaults to <tt>to_table.singularize + "_id"</tt>
      # [<tt>:primary_key</tt>]
      #   The primary key column name on +to_table+. Defaults to +id+.
      # [<tt>:name</tt>]
      #   The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
      # [<tt>:on_delete</tt>]
910
      #   Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
Y
Yves Senn 已提交
911
      # [<tt>:on_update</tt>]
912
      #   Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
913
      def add_foreign_key(from_table, to_table, options = {})
914 915
        return unless supports_foreign_keys?

916
        options = foreign_key_options(from_table, to_table, options)
917 918 919
        at = create_alter_table from_table
        at.add_foreign_key to_table, options

920
        execute schema_creation.accept(at)
921 922
      end

923 924
      # Removes the given foreign key from the table. Any option parameters provided
      # will be used to re-add the foreign key in case of a migration rollback.
925 926
      # It is recommended that you provide any options used when creating the foreign
      # key so that the migration can be reverted properly.
Y
Yves Senn 已提交
927 928 929 930 931 932 933 934 935 936 937 938 939
      #
      # Removes the foreign key on +accounts.branch_id+.
      #
      #   remove_foreign_key :accounts, :branches
      #
      # Removes the foreign key on +accounts.owner_id+.
      #
      #   remove_foreign_key :accounts, column: :owner_id
      #
      # Removes the foreign key named +special_fk_name+ on the +accounts+ table.
      #
      #   remove_foreign_key :accounts, name: :special_fk_name
      #
940
      # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key.
941
      def remove_foreign_key(from_table, options_or_to_table = {})
942 943
        return unless supports_foreign_keys?

944
        fk_name_to_delete = foreign_key_for!(from_table, options_or_to_table).name
Y
Yves Senn 已提交
945

946
        at = create_alter_table from_table
Y
Yves Senn 已提交
947
        at.drop_foreign_key fk_name_to_delete
948

949
        execute schema_creation.accept(at)
950 951
      end

952 953
      # Checks to see if a foreign key exists on a table for a given foreign key definition.
      #
954
      #   # Checks to see if a foreign key exists.
955 956
      #   foreign_key_exists?(:accounts, :branches)
      #
957
      #   # Checks to see if a foreign key on a specified column exists.
958 959
      #   foreign_key_exists?(:accounts, column: :owner_id)
      #
960
      #   # Checks to see if a foreign key with a custom name exists.
961 962 963 964 965 966 967 968
      #   foreign_key_exists?(:accounts, name: "special_fk_name")
      #
      def foreign_key_exists?(from_table, options_or_to_table = {})
        foreign_key_for(from_table, options_or_to_table).present?
      end

      def foreign_key_for(from_table, options_or_to_table = {}) # :nodoc:
        return unless supports_foreign_keys?
969
        foreign_keys(from_table).detect { |fk| fk.defined_for? options_or_to_table }
970
      end
971

972
      def foreign_key_for!(from_table, options_or_to_table = {}) # :nodoc:
973 974
        foreign_key_for(from_table, options_or_to_table) || \
          raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{options_or_to_table}")
975 976
      end

977
      def foreign_key_column_for(table_name) # :nodoc:
978 979 980 981
        prefix = Base.table_name_prefix
        suffix = Base.table_name_suffix
        name = table_name.to_s =~ /#{prefix}(.+)#{suffix}/ ? $1 : table_name.to_s
        "#{name.singularize}_id"
982 983
      end

984 985 986 987 988 989 990
      def foreign_key_options(from_table, to_table, options) # :nodoc:
        options = options.dup
        options[:column] ||= foreign_key_column_for(to_table)
        options[:name]   ||= foreign_key_name(from_table, options)
        options
      end

991
      def dump_schema_information #:nodoc:
992
        versions = ActiveRecord::SchemaMigration.order("version").pluck(:version)
993 994 995
        insert_versions_sql(versions)
      end

996
      def insert_versions_sql(versions) # :nodoc:
997
        sm_table = ActiveRecord::Migrator.schema_migrations_table_name
998

999
        if supports_multi_insert?
1000
          sql = "INSERT INTO #{sm_table} (version) VALUES\n"
1001
          sql << versions.map { |v| "('#{v}')" }.join(",\n")
1002 1003 1004 1005 1006 1007 1008
          sql << ";\n\n"
          sql
        else
          versions.map { |version|
            "INSERT INTO #{sm_table} (version) VALUES ('#{version}');"
          }.join "\n\n"
        end
1009 1010
      end

1011 1012
      # Should not be called normally, but this operation is non-destructive.
      # The migrations module handles this automatically.
1013
      def initialize_schema_migrations_table
1014
        ActiveRecord::SchemaMigration.create_table
1015 1016
      end

1017 1018 1019 1020
      def initialize_internal_metadata_table
        ActiveRecord::InternalMetadata.create_table
      end

1021 1022 1023 1024
      def internal_string_options_for_primary_key # :nodoc:
        { primary_key: true }
      end

1025
      def assume_migrated_upto_version(version, migrations_paths)
1026
        migrations_paths = Array(migrations_paths)
1027
        version = version.to_i
1028
        sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
1029

1030
        migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i)
1031
        paths = migrations_paths.map { |p| "#{p}/[0-9]*_*.rb" }
1032
        versions = Dir[*paths].map do |filename|
1033
          filename.split("/").last.split("_").first.to_i
1034 1035
        end

1036
        unless migrated.include?(version)
1037
          execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')"
1038 1039
        end

1040
        inserting = (versions - migrated).select { |v| v < version }
1041
        if inserting.any?
1042
          if (duplicate = inserting.detect { |v| inserting.count(v) > 1 })
1043
            raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict."
1044
          end
1045
          execute insert_versions_sql(inserting)
1046 1047
        end
      end
1048

1049
      def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
1050 1051
        type = type.to_sym if type
        if native = native_database_types[type]
1052
          column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
1053

1054
          if type == :decimal # ignore limit, use precision and scale
1055
            scale ||= native[:scale]
1056 1057

            if precision ||= native[:precision]
1058 1059 1060 1061 1062
              if scale
                column_type_sql << "(#{precision},#{scale})"
              else
                column_type_sql << "(#{precision})"
              end
1063
            elsif scale
C
Typo  
Chris Patuzzo 已提交
1064
              raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
1065
            end
1066

1067 1068 1069 1070 1071 1072
          elsif [:datetime, :time].include?(type) && precision ||= native[:precision]
            if (0..6) === precision
              column_type_sql << "(#{precision})"
            else
              raise(ActiveRecordError, "No #{native[:name]} type has precision of #{precision}. The allowed range of precision is from 0 to 6")
            end
1073
          elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
1074
            column_type_sql << "(#{limit})"
1075
          end
1076 1077

          column_type_sql
1078
        else
1079
          type.to_s
1080
        end
1081 1082
      end

1083
      # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT.
1084
      # PostgreSQL, MySQL, and Oracle override this for custom DISTINCT syntax - they
1085 1086 1087
      # require the order columns appear in the SELECT.
      #
      #   columns_for_distinct("posts.id", ["posts.created_at desc"])
1088 1089
      #
      def columns_for_distinct(columns, orders) # :nodoc:
1090
        columns
1091
      end
1092

1093
      # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
B
Bogdan 已提交
1094
      # Additional options (like +:null+) are forwarded to #add_column.
1095
      #
B
Bogdan 已提交
1096
      #   add_timestamps(:suppliers, null: true)
1097
      #
1098
      def add_timestamps(table_name, options = {})
1099 1100
        options[:null] = false if options[:null].nil?

1101 1102
        add_column table_name, :created_at, :datetime, options
        add_column table_name, :updated_at, :datetime, options
1103
      end
1104

1105
      # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition.
1106
      #
1107
      #  remove_timestamps(:suppliers)
1108
      #
1109
      def remove_timestamps(table_name, options = {})
1110 1111
        remove_column table_name, :updated_at
        remove_column table_name, :created_at
1112
      end
1113

1114 1115 1116 1117
      def update_table_definition(table_name, base) #:nodoc:
        Table.new(table_name, base)
      end

1118 1119 1120 1121 1122 1123
      def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc:
        if column_name.is_a?(String) && /\W/ === column_name
          column_names = column_name
        else
          column_names = Array(column_name)
        end
1124

1125
        options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
1126 1127

        index_type = options[:type].to_s if options.key?(:type)
1128
        index_type ||= options[:unique] ? "UNIQUE" : ""
1129
        index_name = options[:name].to_s if options.key?(:name)
1130
        index_name ||= index_name(table_name, index_name_options(column_names))
1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143

        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

1144 1145
        validate_index_length!(table_name, index_name, options.fetch(:internal, false))

1146
        if data_source_exists?(table_name) && index_name_exists?(table_name, index_name, false)
1147 1148 1149 1150
          raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
        end
        index_columns = quoted_columns_for_index(column_names, options).join(", ")

1151
        [index_name, index_type, index_columns, index_options, algorithm, using, comment]
1152 1153
      end

1154 1155 1156 1157
      def options_include_default?(options)
        options.include?(:default) && !(options[:null] == false && options[:default].nil?)
      end

1158
      # Changes the comment for a table or removes it if +nil+.
1159
      def change_table_comment(table_name, comment)
1160
        raise NotImplementedError, "#{self.class} does not support changing table comments"
1161 1162
      end

1163
      # Changes the comment for a column or removes it if +nil+.
1164
      def change_column_comment(table_name, column_name, comment) #:nodoc:
1165
        raise NotImplementedError, "#{self.class} does not support changing column comments"
1166 1167
      end

1168
      protected
1169 1170 1171

        def add_index_sort_order(quoted_columns, **options)
          if order = options[:order]
1172 1173
            case order
            when Hash
1174
              quoted_columns.each { |name, column| column << " #{order[name].upcase}" if order[name].present? }
1175
            when String
1176
              quoted_columns.each { |name, column| column << " #{order.upcase}" if order.present? }
1177 1178 1179
            end
          end

1180
          quoted_columns
1181 1182
        end

1183
        # Overridden by the MySQL adapter for supporting index lengths
1184
        def add_options_for_index_columns(quoted_columns, **options)
1185
          if supports_index_sort_order?
1186
            quoted_columns = add_index_sort_order(quoted_columns, options)
1187 1188
          end

1189 1190 1191 1192 1193 1194 1195 1196
          quoted_columns
        end

        def quoted_columns_for_index(column_names, **options)
          return [column_names] if column_names.is_a?(String)

          quoted_columns = Hash[column_names.map { |name| [name, quote_column_name(name).dup] }]
          add_options_for_index_columns(quoted_columns, options).values
1197 1198
        end

1199
        def index_name_for_remove(table_name, options = {})
1200 1201
          return options[:name] if can_remove_index_by_name?(options)

1202 1203 1204
          # if the adapter doesn't support the indexes call the best we can do
          # is return the default index name for the options provided
          return index_name(table_name, options) unless respond_to?(:indexes)
1205

1206
          checks = []
1207

1208
          if options.is_a?(Hash)
1209
            checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
1210 1211 1212 1213
            column_names = Array(options[:column]).map(&:to_s)
          else
            column_names = Array(options).map(&:to_s)
          end
1214

1215
          if column_names.any?
1216
            checks << lambda { |i| i.columns.join("_and_") == column_names.join("_and_") }
1217 1218
          end

1219
          raise ArgumentError, "No name or columns specified" if checks.none?
1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230

          matching_indexes = indexes(table_name).select { |i| checks.all? { |check| check[i] } }

          if matching_indexes.count > 1
            raise ArgumentError, "Multiple indexes found on #{table_name} columns #{column_names}. " \
                                 "Specify an index name from #{matching_indexes.map(&:name).join(', ')}"
          elsif matching_indexes.none?
            raise ArgumentError, "No indexes found on #{table_name} with the options provided."
          else
            matching_indexes.first.name
          end
1231 1232
        end

1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254
        def rename_table_indexes(table_name, new_name)
          indexes(new_name).each do |index|
            generated_index_name = index_name(table_name, column: index.columns)
            if generated_index_name == index.name
              rename_index new_name, generated_index_name, index_name(new_name, column: index.columns)
            end
          end
        end

        def rename_column_indexes(table_name, column_name, new_column_name)
          column_name, new_column_name = column_name.to_s, new_column_name.to_s
          indexes(table_name).each do |index|
            next unless index.columns.include?(new_column_name)
            old_columns = index.columns.dup
            old_columns[old_columns.index(new_column_name)] = column_name
            generated_index_name = index_name(table_name, column: old_columns)
            if generated_index_name == index.name
              rename_index table_name, generated_index_name, index_name(table_name, column: index.columns)
            end
          end
        end

1255
      private
1256 1257 1258
        def create_table_definition(*args)
          TableDefinition.new(*args)
        end
1259

1260 1261
        def create_alter_table(name)
          AlterTable.new create_table_definition(name)
1262 1263
        end

1264 1265 1266 1267
        def index_name_options(column_names) # :nodoc:
          if column_names.is_a?(String)
            column_names = column_names.scan(/\w+/).join("_")
          end
1268

1269 1270 1271 1272 1273 1274 1275 1276 1277
          { column: column_names }
        end

        def foreign_key_name(table_name, options) # :nodoc:
          identifier = "#{table_name}_#{options.fetch(:column)}_fk"
          hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
          options.fetch(:name) do
            "fk_rails_#{hashed_identifier}"
          end
1278
        end
1279

1280 1281
        def validate_index_length!(table_name, new_name, internal = false) # :nodoc:
          max_index_length = internal ? index_name_length : allowed_index_name_length
1282

1283 1284 1285
          if new_name.length > max_index_length
            raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
          end
1286
        end
1287

1288 1289 1290 1291 1292 1293
        def extract_new_default_value(default_or_changes)
          if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to)
            default_or_changes[:to]
          else
            default_or_changes
          end
1294
        end
1295

1296 1297 1298
        def can_remove_index_by_name?(options)
          options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty?
        end
1299 1300
    end
  end
1301
end