schema_statements.rb 30.3 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
        td = create_table_definition
160
        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
      #
      #  # 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.
      #
195 196 197
      # Note that +create_join_table+ does not create any indices by default; you can use
      # its block form to do so yourself:
      #
198 199 200 201
      #   create_join_table :products, :categories do |t|
      #     t.index :products
      #     t.index :categories
      #   end
202
      #
203
      # ====== Add a backend specific option to the generated SQL (MySQL)
A
AvnerCohen 已提交
204
      #  create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
205 206 207 208 209 210 211 212 213
      # 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) || {}
214
        column_options.reverse_merge!(null: false)
215

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

218
        create_table(join_table_name, options.merge!(id: false)) do |td|
219 220 221
          td.integer t1_column, column_options
          td.integer t2_column, column_options
          yield td if block_given?
222 223 224
        end
      end

M
Marc-Andre Lafortune 已提交
225 226 227 228 229 230 231 232 233 234 235
      # 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

236 237 238 239
      # A block for changing columns in +table+.
      #
      #  # change_table() yields a Table instance
      #  change_table(:suppliers) do |t|
A
AvnerCohen 已提交
240
      #    t.column :name, :string, limit: 60
241 242 243
      #    # Other column alterations here
      #  end
      #
244 245 246 247 248 249 250
      # 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.
      #
251 252
      # ====== Add a column
      #  change_table(:suppliers) do |t|
A
AvnerCohen 已提交
253
      #    t.column :name, :string, limit: 60
254 255 256 257
      #  end
      #
      # ====== Add 2 integer columns
      #  change_table(:suppliers) do |t|
A
AvnerCohen 已提交
258
      #    t.integer :width, :height, null: false, default: 0
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
      #  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 已提交
275
      #    t.belongs_to :company, polymorphic: true
276 277 278 279 280 281 282 283 284
      #  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
      #
285
      # ====== Remove several columns
286 287 288 289 290 291 292 293 294 295 296 297
      #  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
298 299 300
      def change_table(table_name, options = {})
        if supports_bulk_alter? && options[:bulk]
          recorder = ActiveRecord::Migration::CommandRecorder.new(self)
301
          yield update_table_definition(table_name, recorder)
302 303
          bulk_change_table(table_name, recorder.commands)
        else
304
          yield update_table_definition(table_name, self)
305
        end
306
      end
307

308
      # Renames a table.
309
      #
310
      #  rename_table('octopuses', 'octopi')
311
      def rename_table(table_name, new_name)
312 313
        raise NotImplementedError, "rename_table is not implemented"
      end
314

315
      # Drops a table from the database.
316 317 318 319
      #
      # 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.
320
      def drop_table(table_name, options = {})
321
        execute "DROP TABLE #{quote_table_name(table_name)}"
322 323
      end

324 325
      # Adds a new column to the named table.
      # See TableDefinition#column for details of the options you can use.
326
      def add_column(table_name, column_name, type, options = {})
327
        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])}"
328 329 330 331
        add_column_options!(add_column_sql, options)
        execute(add_column_sql)
      end

332
      # Removes the given columns from the table definition.
333
      #
334
      #  remove_columns(:suppliers, :qualification, :experience)
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
      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 = {})
350
        execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
351
      end
352

353 354
      # Changes the column's definition according to the new options.
      # See TableDefinition#column for details of the options you can use.
355
      #
A
AvnerCohen 已提交
356
      #  change_column(:suppliers, :name, :string, limit: 80)
357
      #  change_column(:accounts, :description, :text)
358 359 360 361
      def change_column(table_name, column_name, type, options = {})
        raise NotImplementedError, "change_column is not implemented"
      end

362 363 364 365 366 367
      # 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:
368
      #
369
      #  change_column_default(:users, :email, nil)
370
      #
371 372 373 374
      def change_column_default(table_name, column_name, default)
        raise NotImplementedError, "change_column_default is not implemented"
      end

375
      # Renames a column.
376
      #
377
      #  rename_column(:suppliers, :description, :name)
378 379 380 381
      def rename_column(table_name, column_name, new_column_name)
        raise NotImplementedError, "rename_column is not implemented"
      end

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

      # Remove the given index from the table.
      #
435 436 437
      # 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 已提交
438
      #   remove_index :accounts, column: :branch_id
439
      # Remove the index named index_accounts_on_branch_id_and_party_id in the accounts table.
A
AvnerCohen 已提交
440
      #   remove_index :accounts, column: [:branch_id, :party_id]
441
      # Remove the index named by_branch_party in the accounts table.
A
AvnerCohen 已提交
442
      #   remove_index :accounts, name: :by_branch_party
443
      def remove_index(table_name, options = {})
444
        remove_index!(table_name, index_name_for_remove(table_name, options))
445 446 447
      end

      def remove_index!(table_name, index_name) #:nodoc:
448
        execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
449 450 451 452 453 454 455 456 457 458 459 460
      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)
461 462 463
      end

      def index_name(table_name, options) #:nodoc:
R
Rafael Mendonça França 已提交
464
        if Hash === options
465
          if options[:column]
466
            "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
467
          elsif options[:name]
468
            options[:name]
469 470 471 472
          else
            raise ArgumentError, "You must specify the index name"
          end
        else
473
          index_name(table_name, :column => options)
474 475 476
        end
      end

477
      # Verify the existence of an index with a given name.
478 479 480
      #
      # 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.
481
      def index_name_exists?(table_name, index_name, default)
482
        return default unless respond_to?(:indexes)
483 484
        index_name = index_name.to_s
        indexes(table_name).detect { |i| i.name == index_name }
485 486
      end

487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518
      # 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")
519
        remove_column(table_name, "#{ref_name}_type") if options[:polymorphic]
520 521 522
      end
      alias :remove_belongs_to :remove_reference

523 524
      def dump_schema_information #:nodoc:
        sm_table = ActiveRecord::Migrator.schema_migrations_table_name
525

J
Jon Leighton 已提交
526
        ActiveRecord::SchemaMigration.order('version').map { |sm|
527 528
          "INSERT INTO #{sm_table} (version) VALUES ('#{sm.version}');"
        }.join "\n\n"
529 530
      end

531 532
      # Should not be called normally, but this operation is non-destructive.
      # The migrations module handles this automatically.
533
      def initialize_schema_migrations_table
534
        ActiveRecord::SchemaMigration.create_table
535 536
      end

537
      def assume_migrated_upto_version(version, migrations_paths = ActiveRecord::Migrator.migrations_paths)
538
        migrations_paths = Array(migrations_paths)
539
        version = version.to_i
540
        sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
541

542
        migrated = select_values("SELECT version FROM #{sm_table}").map { |v| v.to_i }
543 544
        paths = migrations_paths.map {|p| "#{p}/[0-9]*_*.rb" }
        versions = Dir[*paths].map do |filename|
545 546 547
          filename.split('/').last.split('_').first.to_i
        end

548
        unless migrated.include?(version)
549
          execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')"
550 551 552 553 554 555 556
        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
557
            execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
558 559
            inserted << v
          end
560 561
        end
      end
562

563
      def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
564
        if native = native_database_types[type.to_sym]
565
          column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
566

567
          if type == :decimal # ignore limit, use precision and scale
568
            scale ||= native[:scale]
569 570

            if precision ||= native[:precision]
571 572 573 574 575
              if scale
                column_type_sql << "(#{precision},#{scale})"
              else
                column_type_sql << "(#{precision})"
              end
576
            elsif scale
C
Typo  
Chris Patuzzo 已提交
577
              raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
578
            end
579

580
          elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
581
            column_type_sql << "(#{limit})"
582
          end
583 584

          column_type_sql
585
        else
586
          type
587
        end
588 589
      end

590
      def add_column_options!(sql, options) #:nodoc:
591
        sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
P
Pratik Naik 已提交
592
        # must explicitly check for :null to allow change_column to work on migrations
593 594
        if options[:null] == false
          sql << " NOT NULL"
595
        end
596
      end
597

598 599 600 601 602
      # 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)
603 604
        "DISTINCT #{columns}"
      end
605

606
      # Adds timestamps (created_at and updated_at) columns to the named table.
607
      #
608 609
      #  add_timestamps(:suppliers)
      def add_timestamps(table_name)
610 611
        add_column table_name, :created_at, :datetime
        add_column table_name, :updated_at, :datetime
612
      end
613

614
      # Removes the timestamp columns (created_at and updated_at) from the table definition.
615
      #
616 617
      #  remove_timestamps(:suppliers)
      def remove_timestamps(table_name)
618 619
        remove_column table_name, :updated_at
        remove_column table_name, :created_at
620
      end
621

622
      protected
623 624 625 626
        def add_index_sort_order(option_strings, column_names, options = {})
          if options.is_a?(Hash) && order = options[:order]
            case order
            when Hash
627
              column_names.each {|name| option_strings[name] += " #{order[name].upcase}" if order.has_key?(name)}
628 629 630 631 632 633 634 635
            when String
              column_names.each {|name| option_strings[name] += " #{order.upcase}"}
            end
          end

          return option_strings
        end

636 637
        # Overridden by the mysql adapter for supporting index lengths
        def quoted_columns_for_index(column_names, options = {})
638 639 640 641 642 643 644 645
          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]}
646 647
        end

648 649 650
        def options_include_default?(options)
          options.include?(:default) && !(options[:null] == false && options[:default].nil?)
        end
651

652
        def add_index_options(table_name, column_name, options = {})
653
          column_names = Array(column_name)
654
          index_name   = index_name(table_name, column: column_names)
655 656

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

659 660
            index_type = options[:unique] ? "UNIQUE" : ""
            index_name = options[:name].to_s if options.key?(:name)
661
            max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
662

663 664 665
            if supports_partial_index?
              index_options = options[:where] ? " WHERE #{options[:where]}" : ""
            end
666
          else
667 668 669 670
            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"
671

672 673
              ActiveSupport::Deprecation.warn message
            end
674

675
            index_type = options
676
            max_index_length = allowed_index_name_length
677 678
          end

679 680
          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"
681 682 683 684 685 686
          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(", ")

687
          [index_name, index_type, index_columns, index_options]
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)
694
            if options.is_a?(Hash) && options.has_key?(:name)
695 696 697
              options_without_column = options.dup
              options_without_column.delete :column
              index_name_without_column = index_name(table_name, options_without_column)
698

699 700
              return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false)
            end
701

702 703 704 705 706 707 708
            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)
709 710
          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?
711 712 713
          column_names.map {|column_name| quote_column_name(column_name) }
        end

714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735
        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

736
      private
737
      def create_table_definition
738 739
        TableDefinition.new(self)
      end
740 741 742 743

      def update_table_definition(table_name, base)
        Table.new(table_name, base)
      end
744 745
    end
  end
746
end