schema_statements.rb 39.5 KB
Newer Older
1
require 'active_record/migration/join_table'
2 3
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
      # Truncates a table alias according to the limits of the current adapter.
18
      def table_alias_for(table_name)
19
        table_name[0...table_alias_length].tr('.', '_')
20 21
      end

22 23 24
      # Checks to see if the table +table_name+ exists on the database.
      #
      #   table_exists?(:developers)
25
      #
26
      def table_exists?(table_name)
27
        tables.include?(table_name.to_s)
28 29
      end

30 31 32
      # Returns an array of indexes for the given table.
      # def indexes(table_name, name = nil) end

S
Sebastian Martinez 已提交
33
      # Checks to see if an index exists on a table for a given index definition.
34
      #
35 36 37 38 39
      #   # Check an index exists
      #   index_exists?(:suppliers, :company_id)
      #
      #   # Check an index on multiple columns exists
      #   index_exists?(:suppliers, [:company_id, :company_type])
40
      #
41 42
      #   # Check a unique index exists
      #   index_exists?(:suppliers, :company_id, unique: true)
43
      #
44
      #   # Check an index with a custom name exists
45
      #   index_exists?(:suppliers, :company_id, name: "idx_company_id")
46 47
      #
      def index_exists?(table_name, column_name, options = {})
48 49 50 51 52 53 54 55
        column_names = Array(column_name).map(&:to_s)
        index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, column: column_names)
        checks = []
        checks << lambda { |i| i.name == index_name }
        checks << lambda { |i| i.columns == column_names }
        checks << lambda { |i| i.unique } if options[:unique]

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

58 59
      # Returns an array of Column objects for the table specified by +table_name+.
      # See the concrete implementation for details on the expected parameter values.
60
      def columns(table_name) end
61

62 63
      # Checks to see if a column exists in a given table.
      #
64 65 66 67 68
      #   # Check a column exists
      #   column_exists?(:suppliers, :name)
      #
      #   # Check a column exists of a particular type
      #   column_exists?(:suppliers, :name, :string)
69
      #
70 71 72 73 74
      #   # 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)
75
      #
76
      def column_exists?(table_name, column_name, type = nil, options = {})
77 78
        column_name = column_name.to_s
        columns(table_name).any?{ |c| c.name == column_name &&
79 80 81 82 83 84
                                      (!type                     || c.type == type) &&
                                      (!options.key?(:limit)     || c.limit == options[:limit]) &&
                                      (!options.key?(:precision) || c.precision == options[:precision]) &&
                                      (!options.key?(:scale)     || c.scale == options[:scale]) &&
                                      (!options.key?(:default)   || c.default == options[:default]) &&
                                      (!options.key?(:null)      || c.null == options[:null]) }
85 86
      end

P
Pratik Naik 已提交
87 88 89
      # Creates a new table with the name +table_name+. +table_name+ may either
      # be a String or a Symbol.
      #
90
      # There are two ways to work with +create_table+. You can use the block
91 92 93
      # form or the regular form, like this:
      #
      # === Block form
P
Pratik Naik 已提交
94
      #
95 96 97 98 99 100 101 102
      #   # 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
103
      #
P
Pratik Naik 已提交
104
      # === Block form, with shorthand
105 106 107 108 109 110
      #
      #   # 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 已提交
111
      #
112
      # === Regular form
113 114 115 116 117
      #
      #   # Creates a table called 'suppliers' with no columns.
      #   create_table(:suppliers)
      #   # Add a column to 'suppliers'.
      #   add_column(:suppliers, :name, :string, {limit: 60})
118 119 120
      #
      # The +options+ hash can include the following keys:
      # [<tt>:id</tt>]
121
      #   Whether to automatically add a primary key column. Defaults to true.
X
Xavier Noria 已提交
122
      #   Join tables for +has_and_belongs_to_many+ should set it to false.
123 124
      # [<tt>:primary_key</tt>]
      #   The name of the primary key, if one is to be added automatically.
X
Xavier Noria 已提交
125
      #   Defaults to +id+. If <tt>:id</tt> is false this option is ignored.
126
      #
127 128 129
      #   Note that Active Record models will automatically detect their
      #   primary key. This can be avoided by using +self.primary_key=+ on the model
      #   to define the key explicitly.
130
      #
131 132 133 134
      # [<tt>:options</tt>]
      #   Any extra options you want appended to the table definition.
      # [<tt>:temporary</tt>]
      #   Make a temporary table.
135
      # [<tt>:force</tt>]
136
      #   Set to true to drop the table before creating it.
137
      #   Set to +:cascade+ to drop dependent objects as well.
138
      #   Defaults to false.
139
      # [<tt>:as</tt>]
140 141
      #   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.
142 143
      #
      # ====== Add a backend specific option to the generated SQL (MySQL)
144 145 146
      #
      #   create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
      #
147
      # generates:
148 149 150 151
      #
      #   CREATE TABLE suppliers (
      #     id int(11) DEFAULT NULL auto_increment PRIMARY KEY
      #   ) ENGINE=InnoDB DEFAULT CHARSET=utf8
152 153
      #
      # ====== Rename the primary key column
154 155 156 157 158
      #
      #   create_table(:objects, primary_key: 'guid') do |t|
      #     t.column :name, :string, limit: 80
      #   end
      #
159
      # generates:
160 161 162 163 164
      #
      #   CREATE TABLE objects (
      #     guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
      #     name varchar(80)
      #   )
165 166
      #
      # ====== Do not add a primary key column
167 168 169 170 171 172
      #
      #   create_table(:categories_suppliers, id: false) do |t|
      #     t.column :category_id, :integer
      #     t.column :supplier_id, :integer
      #   end
      #
173
      # generates:
174 175 176 177 178
      #
      #   CREATE TABLE categories_suppliers (
      #     category_id int,
      #     supplier_id int
      #   )
179
      #
180 181 182 183 184 185 186 187 188 189
      # ====== 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
      #
190
      # See also TableDefinition#column for details on how to create columns.
191
      def create_table(table_name, options = {})
192
        td = create_table_definition table_name, options[:temporary], options[:options], options[:as]
193

194 195
        if options[:id] != false && !options[:as]
          pk = options.fetch(:primary_key) do
196
            Base.get_primary_key table_name.to_s.singularize
197
          end
198

199
          td.primary_key pk, options.fetch(:id, :primary_key), options
200
        end
201

202 203
        yield td if block_given?

204
        if options[:force] && table_exists?(table_name)
205
          drop_table(table_name, options)
206 207
        end

208
        result = execute schema_creation.accept td
209 210 211 212 213 214 215 216 217 218 219

        unless supports_indexes_in_create?
          td.indexes.each_pair do |column_name, index_options|
            add_index(table_name, column_name, index_options)
          end
        end

        td.foreign_keys.each_pair do |other_table_name, foreign_key_options|
          add_foreign_key(table_name, other_table_name, foreign_key_options)
        end

220
        result
221
      end
222

223
      # Creates a new join table with the name created using the lexical order of the first two
W
Waseem Ahmad 已提交
224
      # arguments. These arguments can be a String or a Symbol.
225
      #
226 227
      #   # Creates a table called 'assemblies_parts' with no id.
      #   create_join_table(:assemblies, :parts)
228 229 230 231 232 233 234 235 236 237 238 239 240 241
      #
      # You can pass a +options+ hash can include the following keys:
      # [<tt>:table_name</tt>]
      #   Sets the table name overriding the default
      # [<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.
      #
242 243 244
      # Note that +create_join_table+ does not create any indices by default; you can use
      # its block form to do so yourself:
      #
245
      #   create_join_table :products, :categories do |t|
246 247
      #     t.index :product_id
      #     t.index :category_id
248
      #   end
249
      #
250
      # ====== Add a backend specific option to the generated SQL (MySQL)
251 252 253
      #
      #   create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
      #
254
      # generates:
255 256 257 258 259 260
      #
      #   CREATE TABLE assemblies_parts (
      #     assembly_id int NOT NULL,
      #     part_id int NOT NULL,
      #   ) ENGINE=InnoDB DEFAULT CHARSET=utf8
      #
261 262 263 264
      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) || {}
265
        column_options.reverse_merge!(null: false)
266

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

269
        create_table(join_table_name, options.merge!(id: false)) do |td|
270 271 272
          td.integer t1_column, column_options
          td.integer t2_column, column_options
          yield td if block_given?
273 274 275
        end
      end

M
Marc-Andre Lafortune 已提交
276
      # Drops the join table specified by the given arguments.
277
      # See +create_join_table+ for details.
M
Marc-Andre Lafortune 已提交
278 279 280 281 282 283 284 285 286
      #
      # 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.
      # In that case, the block will be used by create_join_table.
      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

287 288
      # A block for changing columns in +table+.
      #
289 290 291 292 293
      #   # change_table() yields a Table instance
      #   change_table(:suppliers) do |t|
      #     t.column :name, :string, limit: 60
      #     # Other column alterations here
      #   end
294
      #
295 296 297
      # The +options+ hash can include the following keys:
      # [<tt>:bulk</tt>]
      #   Set this to true to make this a bulk alter query, such as
298 299
      #
      #     ALTER TABLE `users` ADD COLUMN age INT(11), ADD COLUMN birthdate DATETIME ...
300 301 302
      #
      #   Defaults to false.
      #
303
      # ====== Add a column
304 305 306 307
      #
      #   change_table(:suppliers) do |t|
      #     t.column :name, :string, limit: 60
      #   end
308 309
      #
      # ====== Add 2 integer columns
310 311 312 313
      #
      #   change_table(:suppliers) do |t|
      #     t.integer :width, :height, null: false, default: 0
      #   end
314 315
      #
      # ====== Add created_at/updated_at columns
316 317 318 319
      #
      #   change_table(:suppliers) do |t|
      #     t.timestamps
      #   end
320 321 322
      #
      # ====== Add a foreign key column
      #
323 324 325 326 327
      #   change_table(:suppliers) do |t|
      #     t.references :company
      #   end
      #
      # Creates a <tt>company_id(integer)</tt> column.
328 329
      #
      # ====== Add a polymorphic foreign key column
330
      #
331
      #  change_table(:suppliers) do |t|
A
AvnerCohen 已提交
332
      #    t.belongs_to :company, polymorphic: true
333 334
      #  end
      #
335
      # Creates <tt>company_type(varchar)</tt> and <tt>company_id(integer)</tt> columns.
336 337
      #
      # ====== Remove a column
338
      #
339 340 341 342
      #  change_table(:suppliers) do |t|
      #    t.remove :company
      #  end
      #
343
      # ====== Remove several columns
344
      #
345 346 347 348 349 350
      #  change_table(:suppliers) do |t|
      #    t.remove :company_id
      #    t.remove :width, :height
      #  end
      #
      # ====== Remove an index
351
      #
352 353 354 355
      #  change_table(:suppliers) do |t|
      #    t.remove_index :company_id
      #  end
      #
356
      # See also Table for details on all of the various column transformation.
357 358 359
      def change_table(table_name, options = {})
        if supports_bulk_alter? && options[:bulk]
          recorder = ActiveRecord::Migration::CommandRecorder.new(self)
360
          yield update_table_definition(table_name, recorder)
361 362
          bulk_change_table(table_name, recorder.commands)
        else
363
          yield update_table_definition(table_name, self)
364
        end
365
      end
366

367
      # Renames a table.
368
      #
369 370
      #   rename_table('octopuses', 'octopi')
      #
371
      def rename_table(table_name, new_name)
372 373
        raise NotImplementedError, "rename_table is not implemented"
      end
374

375
      # Drops a table from the database.
376
      #
377 378 379
      # [<tt>:force</tt>]
      #   Set to +:cascade+ to drop dependent objects as well.
      #   Defaults to false.
380 381 382
      # [<tt>:if_exists</tt>]
      #   Set to +true+ to only drop the table if it exists.
      #   Defaults to false.
383 384 385
      #
      # 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.
386
      # In that case, +options+ and the block will be used by create_table.
387
      def drop_table(table_name, options = {})
388
        execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
389 390
      end

391 392
      # Adds a new column to the named table.
      # See TableDefinition#column for details of the options you can use.
393 394 395 396
      #
      # Note: Not all options will be available, generally this command should
      # ignore most of them. In favor of doing a low-level call to simply
      # create a column.
397
      def add_column(table_name, column_name, type, options = {})
398 399 400
        at = create_alter_table table_name
        at.add_column(column_name, type, options)
        execute schema_creation.accept at
401 402
      end

403
      # Removes the given columns from the table definition.
404
      #
405 406
      #   remove_columns(:suppliers, :qualification, :experience)
      #
407 408 409 410 411 412 413 414 415
      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.
      #
416
      #   remove_column(:suppliers, :qualification)
417 418 419 420 421
      #
      # 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.
      # In that case, +type+ and +options+ will be used by add_column.
      def remove_column(table_name, column_name, type = nil, options = {})
422
        execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
423
      end
424

425 426
      # Changes the column's definition according to the new options.
      # See TableDefinition#column for details of the options you can use.
427
      #
428 429 430
      #   change_column(:suppliers, :name, :string, limit: 80)
      #   change_column(:accounts, :description, :text)
      #
431 432 433 434
      def change_column(table_name, column_name, type, options = {})
        raise NotImplementedError, "change_column is not implemented"
      end

435 436 437 438 439 440
      # 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:
441
      #
442
      #   change_column_default(:users, :email, nil)
443
      #
444 445 446 447
      def change_column_default(table_name, column_name, default)
        raise NotImplementedError, "change_column_default is not implemented"
      end

448
      # Sets or removes a +NOT NULL+ constraint on a column. The +null+ flag
C
Carlos Antonio da Silva 已提交
449
      # indicates whether the value can be +NULL+. For example
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
      #
      #   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
      # +NULL+s with some other value. Use that one when enabling the
      # 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

468
      # Renames a column.
469
      #
470 471
      #   rename_column(:suppliers, :description, :name)
      #
472 473 474 475
      def rename_column(table_name, column_name, new_column_name)
        raise NotImplementedError, "rename_column is not implemented"
      end

476
      # Adds a new index to the table. +column_name+ can be a single Symbol, or
477 478
      # an Array of Symbols.
      #
479 480
      # The index will be named after the table and the column name(s), unless
      # you pass <tt>:name</tt> as an option.
481
      #
482
      # ====== Creating a simple index
483 484 485
      #
      #   add_index(:suppliers, :name)
      #
486
      # generates:
487 488
      #
      #   CREATE INDEX suppliers_name_index ON suppliers(name)
489
      #
490
      # ====== Creating a unique index
491 492 493
      #
      #   add_index(:accounts, [:branch_id, :party_id], unique: true)
      #
494
      # generates:
495 496
      #
      #   CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
497
      #
498
      # ====== Creating a named index
499 500 501
      #
      #   add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party')
      #
502
      # generates:
503
      #
504
      #  CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
505 506
      #
      # ====== Creating an index with specific key length
507 508 509
      #
      #   add_index(:accounts, :name, name: 'by_name', length: 10)
      #
510
      # generates:
511
      #
512 513 514 515
      #   CREATE INDEX by_name ON accounts(name(10))
      #
      #   add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
      #
516
      # generates:
517
      #
518 519 520
      #   CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
      #
      # Note: SQLite doesn't support index length.
521 522
      #
      # ====== Creating an index with a sort order (desc or asc, asc is the default)
523 524 525
      #
      #   add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
      #
526
      # generates:
527
      #
528 529 530
      #   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).
531
      #
532
      # ====== Creating a partial index
533 534 535
      #
      #   add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active")
      #
536
      # generates:
537
      #
538
      #   CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
539
      #
D
doabit 已提交
540
      # ====== Creating an index with a specific method
541
      #
542
      #   add_index(:developers, :name, using: 'btree')
543
      #
544
      # generates:
545
      #
546 547
      #   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 已提交
548 549 550
      #
      # Note: only supported by PostgreSQL and MySQL
      #
551 552
      # ====== Creating an index with a specific type
      #
553
      #   add_index(:developers, :name, type: :fulltext)
554
      #
555
      # generates:
556 557 558 559
      #
      #   CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
      #
      # Note: only supported by MySQL. Supported: <tt>:fulltext</tt> and <tt>:spatial</tt> on MyISAM tables.
560
      def add_index(table_name, column_name, options = {})
561 562
        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}"
563 564
      end

565 566 567
      # Removes the given index from the table.
      #
      # Removes the +index_accounts_on_column+ in the +accounts+ table.
568
      #
569
      #   remove_index :accounts, :column
570 571 572
      #
      # Removes the index named +index_accounts_on_branch_id+ in the +accounts+ table.
      #
A
AvnerCohen 已提交
573
      #   remove_index :accounts, column: :branch_id
574 575 576
      #
      # Removes the index named +index_accounts_on_branch_id_and_party_id+ in the +accounts+ table.
      #
A
AvnerCohen 已提交
577
      #   remove_index :accounts, column: [:branch_id, :party_id]
578 579 580
      #
      # Removes the index named +by_branch_party+ in the +accounts+ table.
      #
A
AvnerCohen 已提交
581
      #   remove_index :accounts, name: :by_branch_party
582
      #
583
      def remove_index(table_name, options = {})
584
        remove_index!(table_name, index_name_for_remove(table_name, options))
585 586 587
      end

      def remove_index!(table_name, index_name) #:nodoc:
588
        execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
589 590
      end

591 592 593
      # Renames an index.
      #
      # Rename the +index_people_on_last_name+ index to +index_users_on_last_name+:
594 595
      #
      #   rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
596
      #
597
      def rename_index(table_name, old_name, new_name)
598 599
        validate_index_length!(table_name, new_name)

600 601 602
        # 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
603 604
        add_index(table_name, old_index_def.columns, name: new_name, unique: old_index_def.unique)
        remove_index(table_name, name: old_name)
605 606 607
      end

      def index_name(table_name, options) #:nodoc:
R
Rafael Mendonça França 已提交
608
        if Hash === options
609
          if options[:column]
610
            "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
611
          elsif options[:name]
612
            options[:name]
613 614 615 616
          else
            raise ArgumentError, "You must specify the index name"
          end
        else
617
          index_name(table_name, :column => options)
618 619 620
        end
      end

621
      # Verifies the existence of an index with a given name.
622 623 624
      #
      # 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.
625
      def index_name_exists?(table_name, index_name, default)
626
        return default unless respond_to?(:indexes)
627 628
        index_name = index_name.to_s
        indexes(table_name).detect { |i| i.name == index_name }
629 630
      end

631
      # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
632 633
      # The reference column is an +integer+ by default, the <tt>:type</tt> option can be used to specify
      # a different type.
634 635
      # <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable.
      #
636
      # ====== Create a user_id integer column
637 638
      #
      #   add_reference(:products, :user)
639
      #
640 641 642 643
      # ====== Create a user_id string column
      #
      #   add_reference(:products, :user, type: :string)
      #
644
      # ====== Create a supplier_id and supplier_type columns
645 646
      #
      #   add_belongs_to(:products, :supplier, polymorphic: true)
647
      #
648
      # ====== Create supplier_id, supplier_type columns and appropriate index
649 650
      #
      #   add_reference(:products, :supplier, polymorphic: true, index: true)
651
      #
652 653 654 655
      # ====== Create a supplier_id column and appropriate foreign key
      #
      #   add_reference(:products, :supplier, foreign_key: true)
      #
656 657
      def add_reference(table_name, *args)
        ReferenceDefinition.new(*args).add_to(update_table_definition(table_name, self))
658 659 660 661
      end
      alias :add_belongs_to :add_reference

      # Removes the reference(s). Also removes a +type+ column if one exists.
662
      # <tt>remove_reference</tt> and <tt>remove_belongs_to</tt> are acceptable.
663 664
      #
      # ====== Remove the reference
665 666
      #
      #   remove_reference(:products, :user, index: true)
667 668
      #
      # ====== Remove polymorphic reference
669 670
      #
      #   remove_reference(:products, :supplier, polymorphic: true)
671
      #
672 673 674 675
      # ====== Remove the reference with a foreign key
      #
      #   remove_reference(:products, :user, index: true, foreign_key: true)
      #
676
      def remove_reference(table_name, ref_name, options = {})
677 678 679 680
        if options[:foreign_key]
          reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
          remove_foreign_key(table_name, reference_name)
        end
681

682
        remove_column(table_name, "#{ref_name}_id")
683
        remove_column(table_name, "#{ref_name}_type") if options[:polymorphic]
684 685 686
      end
      alias :remove_belongs_to :remove_reference

Y
Yves Senn 已提交
687 688
      # Returns an array of foreign keys for the given table.
      # The foreign keys are represented as +ForeignKeyDefinition+ objects.
689 690 691 692
      def foreign_keys(table_name)
        raise NotImplementedError, "foreign_keys is not implemented"
      end

Y
Yves Senn 已提交
693 694 695 696
      # 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>.
697 698
      # +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 已提交
699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734
      #
      # ====== Creating a simple foreign key
      #
      #   add_foreign_key :articles, :authors
      #
      # generates:
      #
      #   ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
      #
      # ====== 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:
      #
      #   ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
      #
      # 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>]
      #   Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+
      # [<tt>:on_update</tt>]
      #   Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+
735
      def add_foreign_key(from_table, to_table, options = {})
736 737
        return unless supports_foreign_keys?

738
        options[:column] ||= foreign_key_column_for(to_table)
739

740
        options = {
741
          column: options[:column],
Y
Yves Senn 已提交
742
          primary_key: options[:primary_key],
743
          name: foreign_key_name(from_table, options),
Y
Yves Senn 已提交
744 745
          on_delete: options[:on_delete],
          on_update: options[:on_update]
746 747 748 749
        }
        at = create_alter_table from_table
        at.add_foreign_key to_table, options

750
        execute schema_creation.accept(at)
751 752
      end

Y
Yves Senn 已提交
753 754 755 756 757 758 759 760 761 762 763 764 765 766
      # Removes the given foreign key from the table.
      #
      # 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
      #
767
      def remove_foreign_key(from_table, options_or_to_table = {})
768 769
        return unless supports_foreign_keys?

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

772
        at = create_alter_table from_table
Y
Yves Senn 已提交
773
        at.drop_foreign_key fk_name_to_delete
774

775
        execute schema_creation.accept(at)
776 777
      end

778 779 780 781 782
      # Checks to see if a foreign key exists on a table for a given foreign key definition.
      #
      #   # Check a foreign key exists
      #   foreign_key_exists?(:accounts, :branches)
      #
783
      #   # Check a foreign key on a specified column exists
784 785 786 787 788 789 790 791 792 793 794 795 796
      #   foreign_key_exists?(:accounts, column: :owner_id)
      #
      #   # Check a foreign key with a custom name exists
      #   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?
        foreign_keys(from_table).detect {|fk| fk.defined_for? options_or_to_table }
      end
797

798 799 800 801 802
      def foreign_key_for!(from_table, options_or_to_table = {}) # :nodoc:
        foreign_key_for(from_table, options_or_to_table) or \
          raise ArgumentError, "Table '#{from_table}' has no foreign key for #{options_or_to_table}"
      end

803 804 805 806
      def foreign_key_column_for(table_name) # :nodoc:
        "#{table_name.to_s.singularize}_id"
      end

807 808
      def dump_schema_information #:nodoc:
        sm_table = ActiveRecord::Migrator.schema_migrations_table_name
809

J
Jon Leighton 已提交
810
        ActiveRecord::SchemaMigration.order('version').map { |sm|
811 812
          "INSERT INTO #{sm_table} (version) VALUES ('#{sm.version}');"
        }.join "\n\n"
813 814
      end

815 816
      # Should not be called normally, but this operation is non-destructive.
      # The migrations module handles this automatically.
817
      def initialize_schema_migrations_table
818
        ActiveRecord::SchemaMigration.create_table
819 820
      end

821
      def assume_migrated_upto_version(version, migrations_paths = ActiveRecord::Migrator.migrations_paths)
822
        migrations_paths = Array(migrations_paths)
823
        version = version.to_i
824
        sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
825

826
        migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i)
827 828
        paths = migrations_paths.map {|p| "#{p}/[0-9]*_*.rb" }
        versions = Dir[*paths].map do |filename|
829 830 831
          filename.split('/').last.split('_').first.to_i
        end

832
        unless migrated.include?(version)
833
          execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')"
834 835 836 837 838 839 840
        end

        inserted = Set.new
        (versions - migrated).each do |v|
          if inserted.include?(v)
            raise "Duplicate migration #{v}. Please renumber your migrations to resolve the conflict."
          elsif v < version
841
            execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
842 843
            inserted << v
          end
844 845
        end
      end
846

847
      def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
848
        if native = native_database_types[type.to_sym]
849
          column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
850

851
          if type == :decimal # ignore limit, use precision and scale
852
            scale ||= native[:scale]
853 854

            if precision ||= native[:precision]
855 856 857 858 859
              if scale
                column_type_sql << "(#{precision},#{scale})"
              else
                column_type_sql << "(#{precision})"
              end
860
            elsif scale
C
Typo  
Chris Patuzzo 已提交
861
              raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
862
            end
863

864 865 866 867 868 869
          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
870
          elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
871
            column_type_sql << "(#{limit})"
872
          end
873 874

          column_type_sql
875
        else
876
          type.to_s
877
        end
878 879
      end

880 881 882 883 884
      # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT.
      # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax - they
      # require the order columns appear in the SELECT.
      #
      #   columns_for_distinct("posts.id", ["posts.created_at desc"])
885
      def columns_for_distinct(columns, orders) #:nodoc:
886
        columns
887
      end
888

889 890
      # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
      # Additional options (like <tt>null: false</tt>) are forwarded to #add_column.
891
      #
892
      #   add_timestamps(:suppliers, null: false)
893
      #
894
      def add_timestamps(table_name, options = {})
895 896
        options[:null] = false if options[:null].nil?

897 898
        add_column table_name, :created_at, :datetime, options
        add_column table_name, :updated_at, :datetime, options
899
      end
900

901
      # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition.
902
      #
903
      #  remove_timestamps(:suppliers)
904
      #
905
      def remove_timestamps(table_name, options = {})
906 907
        remove_column table_name, :updated_at
        remove_column table_name, :created_at
908
      end
909

910 911 912 913
      def update_table_definition(table_name, base) #:nodoc:
        Table.new(table_name, base)
      end

914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947
      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

948
      protected
949 950 951 952
        def add_index_sort_order(option_strings, column_names, options = {})
          if options.is_a?(Hash) && order = options[:order]
            case order
            when Hash
953
              column_names.each {|name| option_strings[name] += " #{order[name].upcase}" if order.has_key?(name)}
954 955 956 957 958 959 960 961
            when String
              column_names.each {|name| option_strings[name] += " #{order.upcase}"}
            end
          end

          return option_strings
        end

962
        # Overridden by the MySQL adapter for supporting index lengths
963
        def quoted_columns_for_index(column_names, options = {})
964 965 966 967 968 969 970 971
          option_strings = Hash[column_names.map {|name| [name, '']}]

          # add index sort order if supported
          if supports_index_sort_order?
            option_strings = add_index_sort_order(option_strings, column_names, options)
          end

          column_names.map {|name| quote_column_name(name) + option_strings[name]}
972 973
        end

974 975 976
        def options_include_default?(options)
          options.include?(:default) && !(options[:null] == false && options[:default].nil?)
        end
977

978 979 980 981
        def index_name_for_remove(table_name, options = {})
          index_name = index_name(table_name, options)

          unless index_name_exists?(table_name, index_name, true)
982
            if options.is_a?(Hash) && options.has_key?(:name)
983 984 985
              options_without_column = options.dup
              options_without_column.delete :column
              index_name_without_column = index_name(table_name, options_without_column)
986

987 988
              return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false)
            end
989

990 991 992 993 994 995
            raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
          end

          index_name
        end

996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017
        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

1018
      private
1019
      def create_table_definition(name, temporary = false, options = nil, as = nil)
1020
        TableDefinition.new native_database_types, name, temporary, options, as
1021
      end
1022

1023
      def create_alter_table(name)
1024
        AlterTable.new create_table_definition(name)
1025
      end
1026 1027

      def foreign_key_name(table_name, options) # :nodoc:
1028 1029
        identifier = "#{table_name}_#{options.fetch(:column)}_fk"
        hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
1030
        options.fetch(:name) do
1031
          "fk_rails_#{hashed_identifier}"
1032 1033
        end
      end
1034 1035 1036 1037 1038 1039

      def validate_index_length!(table_name, new_name)
        if new_name.length > allowed_index_name_length
          raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
        end
      end
1040 1041
    end
  end
1042
end