schema_statements.rb 28.5 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
      def table_exists?(table_name)
24
        tables.include?(table_name.to_s)
25 26
      end

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

S
Sebastian Martinez 已提交
30
      # Checks to see if an index exists on a table for a given index definition.
31 32 33 34 35 36 37 38
      #
      #  # Check an index exists
      #  index_exists?(:suppliers, :company_id)
      #
      #  # Check an index on multiple columns exists
      #  index_exists?(:suppliers, [:company_id, :company_type])
      #
      #  # Check a unique index exists
A
AvnerCohen 已提交
39
      #  index_exists?(:suppliers, :company_id, unique: true)
40 41
      #
      #  # Check an index with a custom name exists
A
AvnerCohen 已提交
42
      #  index_exists?(:suppliers, :company_id, name: "idx_company_id"
43
      def index_exists?(table_name, column_name, options = {})
44
        column_names = Array(column_name)
45 46 47 48 49 50 51 52
        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

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

57 58 59 60 61 62 63 64 65
      # Checks to see if a column exists in a given table.
      #
      #  # Check a column exists
      #  column_exists?(:suppliers, :name)
      #
      #  # Check a column exists of a particular type
      #  column_exists?(:suppliers, :name, :string)
      #
      #  # Check a column exists with a specific definition
66
      #  column_exists?(:suppliers, :name, :string, limit: 100)
67
      #  column_exists?(:suppliers, :name, :string, default: 'default')
68 69
      #  column_exists?(:suppliers, :name, :string, null: false)
      #  column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2)
70 71
      def column_exists?(table_name, column_name, type = nil, options = {})
        columns(table_name).any?{ |c| c.name == column_name.to_s &&
72 73 74 75 76 77
                                      (!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]) }
78 79
      end

P
Pratik Naik 已提交
80 81 82
      # Creates a new table with the name +table_name+. +table_name+ may either
      # be a String or a Symbol.
      #
83
      # There are two ways to work with +create_table+. You can use the block
84 85 86
      # form or the regular form, like this:
      #
      # === Block form
P
Pratik Naik 已提交
87 88 89
      #  # create_table() passes a TableDefinition object to the block.
      #  # This form will not only create the table, but also columns for the
      #  # table.
P
Pratik Naik 已提交
90
      #
91
      #  create_table(:suppliers) do |t|
A
AvnerCohen 已提交
92
      #    t.column :name, :string, limit: 60
93 94 95
      #    # Other fields here
      #  end
      #
P
Pratik Naik 已提交
96 97 98
      # === Block form, with shorthand
      #  # You can also use the column types as method calls, rather than calling the column method.
      #  create_table(:suppliers) do |t|
A
AvnerCohen 已提交
99
      #    t.string :name, limit: 60
P
Pratik Naik 已提交
100 101 102
      #    # Other fields here
      #  end
      #
103
      # === Regular form
P
Pratik Naik 已提交
104
      #  # Creates a table called 'suppliers' with no columns.
105
      #  create_table(:suppliers)
P
Pratik Naik 已提交
106
      #  # Add a column to 'suppliers'.
A
AvnerCohen 已提交
107
      #  add_column(:suppliers, :name, :string, {limit: 60})
108 109 110
      #
      # The +options+ hash can include the following keys:
      # [<tt>:id</tt>]
111
      #   Whether to automatically add a primary key column. Defaults to true.
X
Xavier Noria 已提交
112
      #   Join tables for +has_and_belongs_to_many+ should set it to false.
113 114
      # [<tt>:primary_key</tt>]
      #   The name of the primary key, if one is to be added automatically.
X
Xavier Noria 已提交
115
      #   Defaults to +id+. If <tt>:id</tt> is false this option is ignored.
116
      #
X
Xavier Noria 已提交
117
      #   Also note that this just sets the primary key in the table. You additionally
118
      #   need to configure the primary key in the model via +self.primary_key=+.
119 120
      #   Models do NOT auto-detect the primary key from their table definition.
      #
121 122 123 124
      # [<tt>:options</tt>]
      #   Any extra options you want appended to the table definition.
      # [<tt>:temporary</tt>]
      #   Make a temporary table.
125
      # [<tt>:force</tt>]
126
      #   Set to true to drop the table before creating it.
127
      #   Defaults to false.
128 129
      #
      # ====== Add a backend specific option to the generated SQL (MySQL)
A
AvnerCohen 已提交
130
      #  create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
131 132 133 134 135 136
      # generates:
      #  CREATE TABLE suppliers (
      #    id int(11) DEFAULT NULL auto_increment PRIMARY KEY
      #  ) ENGINE=InnoDB DEFAULT CHARSET=utf8
      #
      # ====== Rename the primary key column
A
AvnerCohen 已提交
137 138
      #  create_table(:objects, primary_key: 'guid') do |t|
      #    t.column :name, :string, limit: 80
139 140 141 142 143 144 145 146
      #  end
      # generates:
      #  CREATE TABLE objects (
      #    guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
      #    name varchar(80)
      #  )
      #
      # ====== Do not add a primary key column
A
AvnerCohen 已提交
147
      #  create_table(:categories_suppliers, id: false) do |t|
148 149 150 151
      #    t.column :category_id, :integer
      #    t.column :supplier_id, :integer
      #  end
      # generates:
152
      #  CREATE TABLE categories_suppliers (
153 154 155 156 157
      #    category_id int,
      #    supplier_id int
      #  )
      #
      # See also TableDefinition#column for details on how to create columns.
158
      def create_table(table_name, options = {})
159 160
        td = table_definition
        td.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
161

162
        yield td if block_given?
163

164
        if options[:force] && table_exists?(table_name)
165
          drop_table(table_name, options)
166 167
        end

168
        create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
169
        create_sql << "#{quote_table_name(table_name)} ("
170
        create_sql << td.to_sql
171 172
        create_sql << ") #{options[:options]}"
        execute create_sql
173
        td.indexes.each_pair { |c,o| add_index table_name, c, o }
174
      end
175

176
      # Creates a new join table with the name created using the lexical order of the first two
W
Waseem Ahmad 已提交
177
      # arguments. These arguments can be a String or a Symbol.
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
      #
      #  # Creates a table called 'assemblies_parts' with no id.
      #  create_join_table(:assemblies, :parts)
      #
      # 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.
      #
      # ====== Add a backend specific option to the generated SQL (MySQL)
A
AvnerCohen 已提交
196
      #  create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
197 198 199 200 201 202 203 204 205
      # generates:
      #  CREATE TABLE assemblies_parts (
      #    assembly_id int NOT NULL,
      #    part_id int NOT NULL,
      #  ) ENGINE=InnoDB DEFAULT CHARSET=utf8
      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) || {}
206
        column_options.reverse_merge!(null: false)
207

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

210
        create_table(join_table_name, options.merge!(id: false)) do |td|
211 212 213
          td.integer t1_column, column_options
          td.integer t2_column, column_options
          yield td if block_given?
214 215 216
        end
      end

M
Marc-Andre Lafortune 已提交
217 218 219 220 221 222 223 224 225 226 227
      # Drops the join table specified by the given arguments.
      # See create_join_table for details.
      #
      # 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

228 229 230 231
      # A block for changing columns in +table+.
      #
      #  # change_table() yields a Table instance
      #  change_table(:suppliers) do |t|
A
AvnerCohen 已提交
232
      #    t.column :name, :string, limit: 60
233 234 235
      #    # Other column alterations here
      #  end
      #
236 237 238 239 240 241 242
      # The +options+ hash can include the following keys:
      # [<tt>:bulk</tt>]
      #   Set this to true to make this a bulk alter query, such as
      #   ALTER TABLE `users` ADD COLUMN age INT(11), ADD COLUMN birthdate DATETIME ...
      #
      #   Defaults to false.
      #
243 244
      # ====== Add a column
      #  change_table(:suppliers) do |t|
A
AvnerCohen 已提交
245
      #    t.column :name, :string, limit: 60
246 247 248 249
      #  end
      #
      # ====== Add 2 integer columns
      #  change_table(:suppliers) do |t|
A
AvnerCohen 已提交
250
      #    t.integer :width, :height, null: false, default: 0
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
      #  end
      #
      # ====== Add created_at/updated_at columns
      #  change_table(:suppliers) do |t|
      #    t.timestamps
      #  end
      #
      # ====== Add a foreign key column
      #  change_table(:suppliers) do |t|
      #    t.references :company
      #  end
      #
      # Creates a <tt>company_id(integer)</tt> column
      #
      # ====== Add a polymorphic foreign key column
      #  change_table(:suppliers) do |t|
A
AvnerCohen 已提交
267
      #    t.belongs_to :company, polymorphic: true
268 269 270 271 272 273 274 275 276
      #  end
      #
      # Creates <tt>company_type(varchar)</tt> and <tt>company_id(integer)</tt> columns
      #
      # ====== Remove a column
      #  change_table(:suppliers) do |t|
      #    t.remove :company
      #  end
      #
277
      # ====== Remove several columns
278 279 280 281 282 283 284 285 286 287 288 289
      #  change_table(:suppliers) do |t|
      #    t.remove :company_id
      #    t.remove :width, :height
      #  end
      #
      # ====== Remove an index
      #  change_table(:suppliers) do |t|
      #    t.remove_index :company_id
      #  end
      #
      # See also Table for details on
      # all of the various column transformation
290 291 292 293 294 295 296
      def change_table(table_name, options = {})
        if supports_bulk_alter? && options[:bulk]
          recorder = ActiveRecord::Migration::CommandRecorder.new(self)
          yield Table.new(table_name, recorder)
          bulk_change_table(table_name, recorder.commands)
        else
          yield Table.new(table_name, self)
297
        end
298
      end
299

300
      # Renames a table.
301
      #
302
      #  rename_table('octopuses', 'octopi')
303
      def rename_table(table_name, new_name)
304 305
        raise NotImplementedError, "rename_table is not implemented"
      end
306

307
      # Drops a table from the database.
308 309 310 311
      #
      # 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.
312
      def drop_table(table_name, options = {})
313
        execute "DROP TABLE #{quote_table_name(table_name)}"
314 315
      end

316 317
      # Adds a new column to the named table.
      # See TableDefinition#column for details of the options you can use.
318
      def add_column(table_name, column_name, type, options = {})
319
        add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
320 321 322 323
        add_column_options!(add_column_sql, options)
        execute(add_column_sql)
      end

324
      # Removes the given columns from the table definition.
325
      #
326
      #  remove_columns(:suppliers, :qualification, :experience)
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
      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.
      #
      #  remove_column(:suppliers, :qualification)
      #
      # 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 = {})
342
        execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
343
      end
344

345 346
      # Changes the column's definition according to the new options.
      # See TableDefinition#column for details of the options you can use.
347
      #
A
AvnerCohen 已提交
348
      #  change_column(:suppliers, :name, :string, limit: 80)
349
      #  change_column(:accounts, :description, :text)
350 351 352 353
      def change_column(table_name, column_name, type, options = {})
        raise NotImplementedError, "change_column is not implemented"
      end

354
      # Sets a new default value for a column.
355
      #
356 357
      #  change_column_default(:suppliers, :qualification, 'new')
      #  change_column_default(:accounts, :authorized, 1)
358
      #  change_column_default(:users, :email, nil)
359 360 361 362
      def change_column_default(table_name, column_name, default)
        raise NotImplementedError, "change_column_default is not implemented"
      end

363
      # Renames a column.
364
      #
365
      #  rename_column(:suppliers, :description, :name)
366 367 368 369
      def rename_column(table_name, column_name, new_column_name)
        raise NotImplementedError, "rename_column is not implemented"
      end

370
      # Adds a new index to the table. +column_name+ can be a single Symbol, or
371 372
      # an Array of Symbols.
      #
373 374
      # The index will be named after the table and the column name(s), unless
      # you pass <tt>:name</tt> as an option.
375
      #
376 377 378 379
      # ====== Creating a simple index
      #  add_index(:suppliers, :name)
      # generates
      #  CREATE INDEX suppliers_name_index ON suppliers(name)
380
      #
381
      # ====== Creating a unique index
A
AvnerCohen 已提交
382
      #  add_index(:accounts, [:branch_id, :party_id], unique: true)
383
      # generates
384
      #  CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
385
      #
386
      # ====== Creating a named index
A
AvnerCohen 已提交
387
      #  add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party')
388 389
      # generates
      #  CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
390 391
      #
      # ====== Creating an index with specific key length
A
AvnerCohen 已提交
392
      #  add_index(:accounts, :name, name: 'by_name', length: 10)
393 394 395
      # generates
      #  CREATE INDEX by_name ON accounts(name(10))
      #
A
AvnerCohen 已提交
396
      #  add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
397 398 399 400
      # generates
      #  CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
      #
      # Note: SQLite doesn't support index length
401 402
      #
      # ====== Creating an index with a sort order (desc or asc, asc is the default)
A
AvnerCohen 已提交
403
      #  add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
404 405 406 407 408
      # generates
      #  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)
      #
409
      # ====== Creating a partial index
A
AvnerCohen 已提交
410
      #  add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active")
411 412 413 414 415
      # generates
      #  CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
      #
      # Note: only supported by PostgreSQL
      #
416
      def add_index(table_name, column_name, options = {})
417 418
        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}"
419 420 421 422
      end

      # Remove the given index from the table.
      #
423 424 425
      # Remove the index_accounts_on_column in the accounts table.
      #   remove_index :accounts, :column
      # Remove the index named index_accounts_on_branch_id in the accounts table.
A
AvnerCohen 已提交
426
      #   remove_index :accounts, column: :branch_id
427
      # Remove the index named index_accounts_on_branch_id_and_party_id in the accounts table.
A
AvnerCohen 已提交
428
      #   remove_index :accounts, column: [:branch_id, :party_id]
429
      # Remove the index named by_branch_party in the accounts table.
A
AvnerCohen 已提交
430
      #   remove_index :accounts, name: :by_branch_party
431
      def remove_index(table_name, options = {})
432
        remove_index!(table_name, index_name_for_remove(table_name, options))
433 434 435
      end

      def remove_index!(table_name, index_name) #:nodoc:
436
        execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
437 438 439 440 441 442 443 444 445 446 447 448
      end

      # Rename an index.
      #
      # Rename the index_people_on_last_name index to index_users_on_last_name
      #   rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
      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
        remove_index(table_name, :name => old_name)
        add_index(table_name, old_index_def.columns, :name => new_name, :unique => old_index_def.unique)
449 450 451
      end

      def index_name(table_name, options) #:nodoc:
R
Rafael Mendonça França 已提交
452
        if Hash === options
453
          if options[:column]
454
            "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
455
          elsif options[:name]
456
            options[:name]
457 458 459 460
          else
            raise ArgumentError, "You must specify the index name"
          end
        else
461
          index_name(table_name, :column => options)
462 463 464
        end
      end

465
      # Verify the existence of an index with a given name.
466 467 468
      #
      # 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.
469
      def index_name_exists?(table_name, index_name, default)
470
        return default unless respond_to?(:indexes)
471 472
        index_name = index_name.to_s
        indexes(table_name).detect { |i| i.name == index_name }
473 474
      end

475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506
      # 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
      #  add_reference(:products, :user)
      #
      # ====== Create a supplier_id and supplier_type columns
      #  add_belongs_to(:products, :supplier, polymorphic: true)
      #
      # ====== Create a supplier_id, supplier_type columns and appropriate index
      #  add_reference(:products, :supplier, polymorphic: true, index: true)
      #
      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
        add_index(table_name, polymorphic ? %w[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
      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
      #  remove_reference(:products, :user, index: true)
      #
      # ====== Remove polymorphic reference
      #  remove_reference(:products, :supplier, polymorphic: true)
      #
      def remove_reference(table_name, ref_name, options = {})
        remove_column(table_name, "#{ref_name}_id")
507
        remove_column(table_name, "#{ref_name}_type") if options[:polymorphic]
508 509 510
      end
      alias :remove_belongs_to :remove_reference

511 512 513
      # Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the
      # entire structure of the database.
      def structure_dump
514 515
      end

516 517
      def dump_schema_information #:nodoc:
        sm_table = ActiveRecord::Migrator.schema_migrations_table_name
518

J
Jon Leighton 已提交
519
        ActiveRecord::SchemaMigration.order('version').map { |sm|
520 521
          "INSERT INTO #{sm_table} (version) VALUES ('#{sm.version}');"
        }.join "\n\n"
522 523
      end

524 525
      # Should not be called normally, but this operation is non-destructive.
      # The migrations module handles this automatically.
526
      def initialize_schema_migrations_table
527
        ActiveRecord::SchemaMigration.create_table
528 529
      end

530
      def assume_migrated_upto_version(version, migrations_paths = ActiveRecord::Migrator.migrations_paths)
531
        migrations_paths = Array(migrations_paths)
532
        version = version.to_i
533
        sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
534

535
        migrated = select_values("SELECT version FROM #{sm_table}").map { |v| v.to_i }
536 537
        paths = migrations_paths.map {|p| "#{p}/[0-9]*_*.rb" }
        versions = Dir[*paths].map do |filename|
538 539 540
          filename.split('/').last.split('_').first.to_i
        end

541
        unless migrated.include?(version)
542
          execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')"
543 544 545 546 547 548 549
        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
550
            execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
551 552
            inserted << v
          end
553 554
        end
      end
555

556
      def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
557
        if native = native_database_types[type.to_sym]
558
          column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
559

560
          if type == :decimal # ignore limit, use precision and scale
561
            scale ||= native[:scale]
562 563

            if precision ||= native[:precision]
564 565 566 567 568
              if scale
                column_type_sql << "(#{precision},#{scale})"
              else
                column_type_sql << "(#{precision})"
              end
569
            elsif scale
C
Typo  
Chris Patuzzo 已提交
570
              raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
571
            end
572

573
          elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
574
            column_type_sql << "(#{limit})"
575
          end
576 577

          column_type_sql
578
        else
579
          type
580
        end
581 582
      end

583
      def add_column_options!(sql, options) #:nodoc:
584
        sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
P
Pratik Naik 已提交
585
        # must explicitly check for :null to allow change_column to work on migrations
586 587
        if options[:null] == false
          sql << " NOT NULL"
588
        end
589
      end
590

591 592 593 594 595
      # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
      # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax.
      #
      #   distinct("posts.id", "posts.created_at desc")
      def distinct(columns, order_by)
596 597
        "DISTINCT #{columns}"
      end
598

599
      # Adds timestamps (created_at and updated_at) columns to the named table.
600
      #
601 602
      #  add_timestamps(:suppliers)
      def add_timestamps(table_name)
603 604
        add_column table_name, :created_at, :datetime
        add_column table_name, :updated_at, :datetime
605
      end
606

607
      # Removes the timestamp columns (created_at and updated_at) from the table definition.
608
      #
609 610
      #  remove_timestamps(:suppliers)
      def remove_timestamps(table_name)
611 612
        remove_column table_name, :updated_at
        remove_column table_name, :created_at
613
      end
614

615
      protected
616 617 618 619
        def add_index_sort_order(option_strings, column_names, options = {})
          if options.is_a?(Hash) && order = options[:order]
            case order
            when Hash
620
              column_names.each {|name| option_strings[name] += " #{order[name].upcase}" if order.has_key?(name)}
621 622 623 624 625 626 627 628
            when String
              column_names.each {|name| option_strings[name] += " #{order.upcase}"}
            end
          end

          return option_strings
        end

629 630
        # Overridden by the mysql adapter for supporting index lengths
        def quoted_columns_for_index(column_names, options = {})
631 632 633 634 635 636 637 638
          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]}
639 640
        end

641 642 643
        def options_include_default?(options)
          options.include?(:default) && !(options[:null] == false && options[:default].nil?)
        end
644

645
        def add_index_options(table_name, column_name, options = {})
646
          column_names = Array(column_name)
647
          index_name   = index_name(table_name, column: column_names)
648 649

          if Hash === options # legacy support, since this param was a string
650
            options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal)
651

652 653
            index_type = options[:unique] ? "UNIQUE" : ""
            index_name = options[:name].to_s if options.key?(:name)
654
            max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
655

656 657 658
            if supports_partial_index?
              index_options = options[:where] ? " WHERE #{options[:where]}" : ""
            end
659
          else
660 661 662 663
            if options
              message = "Passing a string as third argument of `add_index` is deprecated and will" +
                " be removed in Rails 4.1." +
                " Use add_index(#{table_name.inspect}, #{column_name.inspect}, unique: true) instead"
664

665 666
              ActiveSupport::Deprecation.warn message
            end
667

668
            index_type = options
669
            max_index_length = allowed_index_name_length
670 671
          end

672 673
          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"
674 675 676 677 678 679
          end
          if 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(", ")

680
          [index_name, index_type, index_columns, index_options]
681 682 683 684 685 686 687 688 689 690 691 692 693
        end

        def index_name_for_remove(table_name, options = {})
          index_name = index_name(table_name, options)

          unless index_name_exists?(table_name, index_name, true)
            raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
          end

          index_name
        end

        def columns_for_remove(table_name, *column_names)
694 695
          ActiveSupport::Deprecation.warn("columns_for_remove is deprecated and will be removed in the future")
          raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") if column_names.blank?
696 697 698
          column_names.map {|column_name| quote_column_name(column_name) }
        end

699 700 701 702
      private
      def table_definition
        TableDefinition.new(self)
      end
703 704
    end
  end
705
end