schema_statements.rb 33.4 KB
Newer Older
1
require 'active_record/migration/join_table'
2

3 4 5
module ActiveRecord
  module ConnectionAdapters # :nodoc:
    module SchemaStatements
6 7
      include ActiveRecord::Migration::JoinTable

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

15
      # Truncates a table alias according to the limits of the current adapter.
16
      def table_alias_for(table_name)
17
        table_name[0...table_alias_length].tr('.', '_')
18 19
      end

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

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

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

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

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

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

190 191
        if options[:id] != false && !options[:as]
          pk = options.fetch(:primary_key) do
192
            Base.get_primary_key table_name.to_s.singularize
193
          end
194

195
          td.primary_key pk, options.fetch(:id, :primary_key), options
196
        end
197

198 199
        yield td if block_given?

200
        if options[:force] && table_exists?(table_name)
201
          drop_table(table_name, options)
202 203
        end

204
        result = execute schema_creation.accept td
205
        td.indexes.each_pair { |c, o| add_index(table_name, c, o) } unless supports_indexes_in_create?
206
        result
207
      end
208

209
      # Creates a new join table with the name created using the lexical order of the first two
W
Waseem Ahmad 已提交
210
      # arguments. These arguments can be a String or a Symbol.
211
      #
212 213
      #   # Creates a table called 'assemblies_parts' with no id.
      #   create_join_table(:assemblies, :parts)
214 215 216 217 218 219 220 221 222 223 224 225 226 227
      #
      # 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.
      #
228 229 230
      # Note that +create_join_table+ does not create any indices by default; you can use
      # its block form to do so yourself:
      #
231
      #   create_join_table :products, :categories do |t|
232 233
      #     t.index :product_id
      #     t.index :category_id
234
      #   end
235
      #
236
      # ====== Add a backend specific option to the generated SQL (MySQL)
237 238 239
      #
      #   create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
      #
240
      # generates:
241 242 243 244 245 246
      #
      #   CREATE TABLE assemblies_parts (
      #     assembly_id int NOT NULL,
      #     part_id int NOT NULL,
      #   ) ENGINE=InnoDB DEFAULT CHARSET=utf8
      #
247 248 249 250
      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) || {}
251
        column_options.reverse_merge!(null: false)
252

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

255
        create_table(join_table_name, options.merge!(id: false)) do |td|
256 257 258
          td.integer t1_column, column_options
          td.integer t2_column, column_options
          yield td if block_given?
259 260 261
        end
      end

M
Marc-Andre Lafortune 已提交
262
      # Drops the join table specified by the given arguments.
263
      # See +create_join_table+ for details.
M
Marc-Andre Lafortune 已提交
264 265 266 267 268 269 270 271 272
      #
      # 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

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

353
      # Renames a table.
354
      #
355 356
      #   rename_table('octopuses', 'octopi')
      #
357
      def rename_table(table_name, new_name)
358 359
        raise NotImplementedError, "rename_table is not implemented"
      end
360

361
      # Drops a table from the database.
362 363 364 365
      #
      # Although this command ignores +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.
      # In that case, +options+ and the block will be used by create_table.
366
      def drop_table(table_name, options = {})
367
        execute "DROP TABLE #{quote_table_name(table_name)}"
368 369
      end

370 371
      # Adds a new column to the named table.
      # See TableDefinition#column for details of the options you can use.
372
      def add_column(table_name, column_name, type, options = {})
373 374 375
        at = create_alter_table table_name
        at.add_column(column_name, type, options)
        execute schema_creation.accept at
376 377
      end

378
      # Removes the given columns from the table definition.
379
      #
380 381
      #   remove_columns(:suppliers, :qualification, :experience)
      #
382 383 384 385 386 387 388 389 390
      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.
      #
391
      #   remove_column(:suppliers, :qualification)
392 393 394 395 396
      #
      # 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 = {})
397
        execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
398
      end
399

400 401
      # Changes the column's definition according to the new options.
      # See TableDefinition#column for details of the options you can use.
402
      #
403 404 405
      #   change_column(:suppliers, :name, :string, limit: 80)
      #   change_column(:accounts, :description, :text)
      #
406 407 408 409
      def change_column(table_name, column_name, type, options = {})
        raise NotImplementedError, "change_column is not implemented"
      end

410 411 412 413 414 415
      # 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:
416
      #
417
      #   change_column_default(:users, :email, nil)
418
      #
419 420 421 422
      def change_column_default(table_name, column_name, default)
        raise NotImplementedError, "change_column_default is not implemented"
      end

423
      # Sets or removes a +NOT NULL+ constraint on a column. The +null+ flag
C
Carlos Antonio da Silva 已提交
424
      # indicates whether the value can be +NULL+. For example
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
      #
      #   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

443
      # Renames a column.
444
      #
445 446
      #   rename_column(:suppliers, :description, :name)
      #
447 448 449 450
      def rename_column(table_name, column_name, new_column_name)
        raise NotImplementedError, "rename_column is not implemented"
      end

451
      # Adds a new index to the table. +column_name+ can be a single Symbol, or
452 453
      # an Array of Symbols.
      #
454 455
      # The index will be named after the table and the column name(s), unless
      # you pass <tt>:name</tt> as an option.
456
      #
457
      # ====== Creating a simple index
458 459 460
      #
      #   add_index(:suppliers, :name)
      #
461
      # generates:
462 463
      #
      #   CREATE INDEX suppliers_name_index ON suppliers(name)
464
      #
465
      # ====== Creating a unique index
466 467 468
      #
      #   add_index(:accounts, [:branch_id, :party_id], unique: true)
      #
469
      # generates:
470 471
      #
      #   CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
472
      #
473
      # ====== Creating a named index
474 475 476
      #
      #   add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party')
      #
477
      # generates:
478
      #
479
      #  CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
480 481
      #
      # ====== Creating an index with specific key length
482 483 484
      #
      #   add_index(:accounts, :name, name: 'by_name', length: 10)
      #
485
      # generates:
486
      #
487 488 489 490
      #   CREATE INDEX by_name ON accounts(name(10))
      #
      #   add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
      #
491
      # generates:
492
      #
493 494 495
      #   CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
      #
      # Note: SQLite doesn't support index length.
496 497
      #
      # ====== Creating an index with a sort order (desc or asc, asc is the default)
498 499 500
      #
      #   add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
      #
501
      # generates:
502
      #
503 504 505
      #   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).
506
      #
507
      # ====== Creating a partial index
508 509 510
      #
      #   add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active")
      #
511
      # generates:
512
      #
513
      #   CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
514
      #
D
doabit 已提交
515
      # ====== Creating an index with a specific method
516
      #
517
      #   add_index(:developers, :name, using: 'btree')
518
      #
519
      # generates:
520
      #
521 522
      #   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 已提交
523 524 525
      #
      # Note: only supported by PostgreSQL and MySQL
      #
526 527
      # ====== Creating an index with a specific type
      #
528
      #   add_index(:developers, :name, type: :fulltext)
529
      #
530
      # generates:
531 532 533 534
      #
      #   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.
535
      def add_index(table_name, column_name, options = {})
536 537
        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}"
538 539
      end

540 541 542
      # Removes the given index from the table.
      #
      # Removes the +index_accounts_on_column+ in the +accounts+ table.
543
      #
544
      #   remove_index :accounts, :column
545 546 547
      #
      # Removes the index named +index_accounts_on_branch_id+ in the +accounts+ table.
      #
A
AvnerCohen 已提交
548
      #   remove_index :accounts, column: :branch_id
549 550 551
      #
      # Removes the index named +index_accounts_on_branch_id_and_party_id+ in the +accounts+ table.
      #
A
AvnerCohen 已提交
552
      #   remove_index :accounts, column: [:branch_id, :party_id]
553 554 555
      #
      # Removes the index named +by_branch_party+ in the +accounts+ table.
      #
A
AvnerCohen 已提交
556
      #   remove_index :accounts, name: :by_branch_party
557
      #
558
      def remove_index(table_name, options = {})
559
        remove_index!(table_name, index_name_for_remove(table_name, options))
560 561 562
      end

      def remove_index!(table_name, index_name) #:nodoc:
563
        execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
564 565
      end

566 567 568
      # Renames an index.
      #
      # Rename the +index_people_on_last_name+ index to +index_users_on_last_name+:
569 570
      #
      #   rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
571
      #
572 573 574 575
      def rename_index(table_name, old_name, new_name)
        # 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
576 577
        add_index(table_name, old_index_def.columns, name: new_name, unique: old_index_def.unique)
        remove_index(table_name, name: old_name)
578 579 580
      end

      def index_name(table_name, options) #:nodoc:
R
Rafael Mendonça França 已提交
581
        if Hash === options
582
          if options[:column]
583
            "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
584
          elsif options[:name]
585
            options[:name]
586 587 588 589
          else
            raise ArgumentError, "You must specify the index name"
          end
        else
590
          index_name(table_name, :column => options)
591 592 593
        end
      end

594
      # Verifies the existence of an index with a given name.
595 596 597
      #
      # 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.
598
      def index_name_exists?(table_name, index_name, default)
599
        return default unless respond_to?(:indexes)
600 601
        index_name = index_name.to_s
        indexes(table_name).detect { |i| i.name == index_name }
602 603
      end

604 605 606 607
      # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
      # <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable.
      #
      # ====== Create a user_id column
608 609
      #
      #   add_reference(:products, :user)
610 611
      #
      # ====== Create a supplier_id and supplier_type columns
612 613
      #
      #   add_belongs_to(:products, :supplier, polymorphic: true)
614 615
      #
      # ====== Create a supplier_id, supplier_type columns and appropriate index
616 617
      #
      #   add_reference(:products, :supplier, polymorphic: true, index: true)
618 619 620 621 622 623
      #
      def add_reference(table_name, ref_name, options = {})
        polymorphic = options.delete(:polymorphic)
        index_options = options.delete(:index)
        add_column(table_name, "#{ref_name}_id", :integer, options)
        add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
624
        add_index(table_name, polymorphic ? %w[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
625 626 627 628 629 630 631
      end
      alias :add_belongs_to :add_reference

      # Removes the reference(s). Also removes a +type+ column if one exists.
      # <tt>remove_reference</tt>, <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
      #
      # ====== Remove the reference
632 633
      #
      #   remove_reference(:products, :user, index: true)
634 635
      #
      # ====== Remove polymorphic reference
636 637
      #
      #   remove_reference(:products, :supplier, polymorphic: true)
638 639 640
      #
      def remove_reference(table_name, ref_name, options = {})
        remove_column(table_name, "#{ref_name}_id")
641
        remove_column(table_name, "#{ref_name}_type") if options[:polymorphic]
642 643 644
      end
      alias :remove_belongs_to :remove_reference

645 646 647 648 649
      def foreign_keys(table_name)
        raise NotImplementedError, "foreign_keys is not implemented"
      end

      def add_foreign_key(from_table, to_table, options = {})
650 651
        primary_key = options.fetch(:primary_key, "id")

652 653
        options = {
          column: options.fetch(:column),
654
          primary_key: primary_key,
655 656
          name: foreign_key_name(from_table, options),
          dependent: options.fetch(:dependent, nil)
657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677
        }
        at = create_alter_table from_table
        at.add_foreign_key to_table, options

        execute schema_creation.accept at
      end

      def remove_foreign_key(from_table, options = {})
        at = create_alter_table from_table
        at.drop_foreign_key foreign_key_name(from_table, options)

        execute schema_creation.accept at
      end

      def foreign_key_name(table_name, options) # :nodoc:
        options.fetch(:name) do
          column_name = options.fetch(:column)
          "#{table_name}_#{column_name}_fk"
        end
      end

678 679
      def dump_schema_information #:nodoc:
        sm_table = ActiveRecord::Migrator.schema_migrations_table_name
680

J
Jon Leighton 已提交
681
        ActiveRecord::SchemaMigration.order('version').map { |sm|
682 683
          "INSERT INTO #{sm_table} (version) VALUES ('#{sm.version}');"
        }.join "\n\n"
684 685
      end

686 687
      # Should not be called normally, but this operation is non-destructive.
      # The migrations module handles this automatically.
688
      def initialize_schema_migrations_table
689
        ActiveRecord::SchemaMigration.create_table
690 691
      end

692
      def assume_migrated_upto_version(version, migrations_paths = ActiveRecord::Migrator.migrations_paths)
693
        migrations_paths = Array(migrations_paths)
694
        version = version.to_i
695
        sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
696

697
        migrated = select_values("SELECT version FROM #{sm_table}").map { |v| v.to_i }
698 699
        paths = migrations_paths.map {|p| "#{p}/[0-9]*_*.rb" }
        versions = Dir[*paths].map do |filename|
700 701 702
          filename.split('/').last.split('_').first.to_i
        end

703
        unless migrated.include?(version)
704
          execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')"
705 706 707 708 709 710 711
        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
712
            execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
713 714
            inserted << v
          end
715 716
        end
      end
717

718
      def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
719
        if native = native_database_types[type.to_sym]
720
          column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
721

722
          if type == :decimal # ignore limit, use precision and scale
723
            scale ||= native[:scale]
724 725

            if precision ||= native[:precision]
726 727 728 729 730
              if scale
                column_type_sql << "(#{precision},#{scale})"
              else
                column_type_sql << "(#{precision})"
              end
731
            elsif scale
C
Typo  
Chris Patuzzo 已提交
732
              raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
733
            end
734

735
          elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
736
            column_type_sql << "(#{limit})"
737
          end
738 739

          column_type_sql
740
        else
741
          type.to_s
742
        end
743 744
      end

745 746 747 748 749
      # 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"])
750
      def columns_for_distinct(columns, orders) #:nodoc:
751
        columns
752
      end
753

754 755 756
      # Adds timestamps (+created_at+ and +updated_at+) columns to the named table.
      #
      #   add_timestamps(:suppliers)
757
      #
758
      def add_timestamps(table_name)
759 760
        add_column table_name, :created_at, :datetime
        add_column table_name, :updated_at, :datetime
761
      end
762

763
      # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition.
764
      #
765
      #  remove_timestamps(:suppliers)
766
      #
767
      def remove_timestamps(table_name)
768 769
        remove_column table_name, :updated_at
        remove_column table_name, :created_at
770
      end
771

772 773 774 775
      def update_table_definition(table_name, base) #:nodoc:
        Table.new(table_name, base)
      end

776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809
      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

810
      protected
811 812 813 814
        def add_index_sort_order(option_strings, column_names, options = {})
          if options.is_a?(Hash) && order = options[:order]
            case order
            when Hash
815
              column_names.each {|name| option_strings[name] += " #{order[name].upcase}" if order.has_key?(name)}
816 817 818 819 820 821 822 823
            when String
              column_names.each {|name| option_strings[name] += " #{order.upcase}"}
            end
          end

          return option_strings
        end

824
        # Overridden by the MySQL adapter for supporting index lengths
825
        def quoted_columns_for_index(column_names, options = {})
826 827 828 829 830 831 832 833
          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]}
834 835
        end

836 837 838
        def options_include_default?(options)
          options.include?(:default) && !(options[:null] == false && options[:default].nil?)
        end
839

840 841 842 843
        def index_name_for_remove(table_name, options = {})
          index_name = index_name(table_name, options)

          unless index_name_exists?(table_name, index_name, true)
844
            if options.is_a?(Hash) && options.has_key?(:name)
845 846 847
              options_without_column = options.dup
              options_without_column.delete :column
              index_name_without_column = index_name(table_name, options_without_column)
848

849 850
              return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false)
            end
851

852 853 854 855 856 857
            raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
          end

          index_name
        end

858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879
        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

880
      private
881 882
      def create_table_definition(name, temporary, options, as = nil)
        TableDefinition.new native_database_types, name, temporary, options, as
883
      end
884

885 886 887
      def create_alter_table(name)
        AlterTable.new create_table_definition(name, false, {})
      end
888 889
    end
  end
890
end