schema_definitions.rb 21.0 KB
Newer Older
1
require 'date'
2
require 'set'
3 4
require 'bigdecimal'
require 'bigdecimal/util'
5

6 7
module ActiveRecord
  module ConnectionAdapters #:nodoc:
8 9 10
    # Abstract representation of an index definition on a table. Instances of
    # this type are typically created and returned by methods in database
    # adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
11
    class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using) #:nodoc:
12 13
    end

P
Pratik Naik 已提交
14 15 16 17
    # Abstract representation of a column definition. Instances of this type
    # are typically created by methods in TableDefinition, and added to the
    # +columns+ attribute of said TableDefinition object, in order to be used
    # for generating a number of table creation or table changing SQL statements.
18
    class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key, :sql_type, :cast_type) #:nodoc:
19

A
Aaron Patterson 已提交
20
      def primary_key?
21
        primary_key || type.to_sym == :primary_key
A
Aaron Patterson 已提交
22
      end
23 24
    end

25 26 27
    class ChangeColumnDefinition < Struct.new(:column, :type, :options) #:nodoc:
    end

28
    class ForeignKeyDefinition < Struct.new(:from_table, :to_table, :options) #:nodoc:
29 30 31 32 33 34 35 36 37
      def name
        options[:name]
      end

      def column
        options[:column]
      end

      def primary_key
Y
Yves Senn 已提交
38
        options[:primary_key] || default_primary_key
39
      end
40

41 42
      def on_delete
        options[:on_delete]
43
      end
Y
Yves Senn 已提交
44 45 46 47

      def on_update
        options[:on_update]
      end
Y
Yves Senn 已提交
48 49 50 51 52 53 54 55 56

      def custom_primary_key?
        options[:primary_key] != default_primary_key
      end

      private
      def default_primary_key
        "id"
      end
57 58
    end

59 60 61 62
    module TimestampDefaultDeprecation # :nodoc:
      def emit_warning_if_null_unspecified(options)
        return if options.key?(:null)

63 64 65 66 67
        ActiveSupport::Deprecation.warn \
          "`timestamp` was called without specifying an option for `null`. In Rails " \
          "5.0, this behavior will change to `null: false`. You should manually " \
          "specify `null: true` to prevent the behavior of your existing migrations " \
          "from changing."
68 69 70
      end
    end

P
Pratik Naik 已提交
71 72 73
    # Represents the schema of an SQL table in an abstract way. This class
    # provides methods for manipulating the schema representation.
    #
74 75
    # Inside migration files, the +t+ object in +create_table+
    # is actually of this type:
P
Pratik Naik 已提交
76 77
    #
    #   class SomeMigration < ActiveRecord::Migration
A
Akira Matsuda 已提交
78
    #     def up
P
Pratik Naik 已提交
79 80 81 82
    #       create_table :foo do |t|
    #         puts t.class  # => "ActiveRecord::ConnectionAdapters::TableDefinition"
    #       end
    #     end
P
Pratik Naik 已提交
83
    #
A
Akira Matsuda 已提交
84
    #     def down
P
Pratik Naik 已提交
85 86 87 88 89 90
    #       ...
    #     end
    #   end
    #
    # The table definitions
    # The Columns are stored as a ColumnDefinition in the +columns+ attribute.
91
    class TableDefinition
92 93
      include TimestampDefaultDeprecation

P
Pratik Naik 已提交
94 95
      # An array of ColumnDefinition objects, representing the column changes
      # that have been defined.
96
      attr_accessor :indexes
97
      attr_reader :name, :temporary, :options, :as
98

99
      def initialize(types, name, temporary, options, as = nil)
100
        @columns_hash = {}
101
        @indexes = {}
102
        @native = types
103 104
        @temporary = temporary
        @options = options
105
        @as = as
106
        @name = name
107 108
      end

109 110
      def columns; @columns_hash.values; end

111 112
      # Appends a primary key definition to the table definition.
      # Can be called multiple times, but this is probably not a good idea.
A
Aaron Patterson 已提交
113
      def primary_key(name, type = :primary_key, options = {})
114
        column(name, type, options.merge(:primary_key => true))
115
      end
116 117

      # Returns a ColumnDefinition for the column with name +name+.
118
      def [](name)
119
        @columns_hash[name.to_s]
120 121
      end

122
      # Instantiates a new column for the table.
123 124
      # The +type+ parameter is normally one of the migrations native types,
      # which is one of the following:
125
      # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
126 127 128
      # <tt>:integer</tt>, <tt>:float</tt>, <tt>:decimal</tt>,
      # <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
      # <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>.
129
      #
130 131 132 133
      # You may use a type not in this list as long as it is supported by your
      # database (for example, "polygon" in MySQL), but this will not be database
      # agnostic and should usually be avoided.
      #
134
      # Available options are (none of these exists by default):
135
      # * <tt>:limit</tt> -
136
      #   Requests a maximum column length. This is number of characters for <tt>:string</tt> and
137
      #   <tt>:text</tt> columns and number of bytes for <tt>:binary</tt> and <tt>:integer</tt> columns.
138
      # * <tt>:default</tt> -
139
      #   The column's default value. Use nil for NULL.
140
      # * <tt>:null</tt> -
141
      #   Allows or disallows +NULL+ values in the column. This option could
142
      #   have been named <tt>:null_allowed</tt>.
143
      # * <tt>:precision</tt> -
144
      #   Specifies the precision for a <tt>:decimal</tt> column.
145
      # * <tt>:scale</tt> -
146
      #   Specifies the scale for a <tt>:decimal</tt> column.
147 148
      # * <tt>:index</tt> -
      #   Create an index for the column. Can be either <tt>true</tt> or an options hash.
149
      #
150 151
      # Note: The precision is the total number of significant digits
      # and the scale is the number of digits that can be stored following
P
Pratik Naik 已提交
152 153 154 155
      # the decimal point. For example, the number 123.45 has a precision of 5
      # and a scale of 2. A decimal with a precision of 5 and a scale of 2 can
      # range from -999.99 to 999.99.
      #
156 157 158 159 160
      # Please be aware of different RDBMS implementations behavior with
      # <tt>:decimal</tt> columns:
      # * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
      #   <tt>:precision</tt>, and makes no comments about the requirements of
      #   <tt>:precision</tt>.
161
      # * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
162
      #   Default is (10,0).
163
      # * PostgreSQL: <tt>:precision</tt> [1..infinity],
164
      #   <tt>:scale</tt> [0..infinity]. No default.
165
      # * SQLite2: Any <tt>:precision</tt> and <tt>:scale</tt> may be used.
166
      #   Internal storage as strings. No default.
167
      # * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
168
      #   but the maximum supported <tt>:precision</tt> is 16. No default.
169
      # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
170
      #   Default is (38,0).
171
      # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
172
      #   Default unknown.
173
      # * SqlServer?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
174
      #   Default (38,0).
175 176 177
      #
      # This method returns <tt>self</tt>.
      #
178
      # == Examples
179
      #  # Assuming +td+ is an instance of TableDefinition
180
      #  td.column(:granted, :boolean)
P
Pratik Naik 已提交
181
      #  # granted BOOLEAN
182
      #
A
AvnerCohen 已提交
183
      #  td.column(:picture, :binary, limit: 2.megabytes)
P
Pratik Naik 已提交
184
      #  # => picture BLOB(2097152)
185
      #
A
AvnerCohen 已提交
186
      #  td.column(:sales_stage, :string, limit: 20, default: 'new', null: false)
P
Pratik Naik 已提交
187
      #  # => sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL
188
      #
A
AvnerCohen 已提交
189
      #  td.column(:bill_gates_money, :decimal, precision: 15, scale: 2)
P
Pratik Naik 已提交
190
      #  # => bill_gates_money DECIMAL(15,2)
191
      #
A
AvnerCohen 已提交
192
      #  td.column(:sensor_reading, :decimal, precision: 30, scale: 20)
P
Pratik Naik 已提交
193
      #  # => sensor_reading DECIMAL(30,20)
194 195 196
      #
      #  # While <tt>:scale</tt> defaults to zero on most databases, it
      #  # probably wouldn't hurt to include it.
A
AvnerCohen 已提交
197
      #  td.column(:huge_integer, :decimal, precision: 30)
P
Pratik Naik 已提交
198
      #  # => huge_integer DECIMAL(30)
199
      #
P
Pratik Naik 已提交
200 201 202 203
      #  # Defines a column with a database-specific type.
      #  td.column(:foo, 'polygon')
      #  # => foo polygon
      #
204 205
      # == Short-hand examples
      #
P
Pratik Naik 已提交
206
      # Instead of calling +column+ directly, you can also work with the short-hand definitions for the default types.
207 208 209 210 211
      # They use the type as the method name instead of as a parameter and allow for multiple columns to be defined
      # in a single statement.
      #
      # What can be written like this with the regular calls to column:
      #
212
      #   create_table :products do |t|
213 214 215 216 217 218 219
      #     t.column :shop_id,     :integer
      #     t.column :creator_id,  :integer
      #     t.column :item_number, :string
      #     t.column :name,        :string, default: "Untitled"
      #     t.column :value,       :string, default: "Untitled"
      #     t.column :created_at,  :datetime
      #     t.column :updated_at,  :datetime
220
      #   end
221
      #   add_index :products, :item_number
222
      #
223
      # can also be written as follows using the short-hand:
224 225 226
      #
      #   create_table :products do |t|
      #     t.integer :shop_id, :creator_id
227
      #     t.string  :item_number, index: true
A
AvnerCohen 已提交
228
      #     t.string  :name, :value, default: "Untitled"
229 230 231
      #     t.timestamps
      #   end
      #
232
      # There's a short-hand method for each of the type values declared at the top. And then there's
233
      # TableDefinition#timestamps that'll add +created_at+ and +updated_at+ as datetimes.
234 235
      #
      # TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type
236
      # column if the <tt>:polymorphic</tt> option is supplied. If <tt>:polymorphic</tt> is a hash of
237 238
      # options, these will be used when creating the <tt>_type</tt> column. The <tt>:index</tt> option
      # will also create an index, similar to calling <tt>add_index</tt>. So what can be written like this:
239 240 241 242
      #
      #   create_table :taggings do |t|
      #     t.integer :tag_id, :tagger_id, :taggable_id
      #     t.string  :tagger_type
A
AvnerCohen 已提交
243
      #     t.string  :taggable_type, default: 'Photo'
244
      #   end
A
AvnerCohen 已提交
245
      #   add_index :taggings, :tag_id, name: 'index_taggings_on_tag_id'
246
      #   add_index :taggings, [:tagger_id, :tagger_type]
247 248 249 250
      #
      # Can also be written as follows using references:
      #
      #   create_table :taggings do |t|
A
AvnerCohen 已提交
251 252 253
      #     t.references :tag, index: { name: 'index_taggings_on_tag_id' }
      #     t.references :tagger, polymorphic: true, index: true
      #     t.references :taggable, polymorphic: { default: 'Photo' }
254
      #   end
255
      def column(name, type, options = {})
256 257 258
        name = name.to_s
        type = type.to_sym

259 260 261 262
        if primary_key_column_name == name
          raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table."
        end

263 264
        index_options = options.delete(:index)
        index(name, index_options.is_a?(Hash) ? index_options : {}) if index_options
265
        @columns_hash[name] = new_column_definition(name, type, options)
266 267
        self
      end
268

269 270 271 272
      def remove_column(name)
        @columns_hash.delete name.to_s
      end

273 274 275 276 277 278
      [:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type|
        define_method column_type do |*args|
          options = args.extract_options!
          column_names = args
          column_names.each { |name| column(name, column_type, options) }
        end
279
      end
280

281 282
      # Adds index options to the indexes hash, keyed by column name
      # This is primarily used to track indexes that need to be created after the table
283
      #
A
AvnerCohen 已提交
284
      #   index(:account_id, name: 'index_projects_on_account_id')
285 286 287
      def index(column_name, options = {})
        indexes[column_name] = options
      end
288 289

      # Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
290
      # <tt>:updated_at</tt> to the table.
291
      def timestamps(*args)
292
        options = args.extract_options!
293
        emit_warning_if_null_unspecified(options)
294 295
        column(:created_at, :datetime, options)
        column(:updated_at, :datetime, options)
296 297
      end

298 299 300 301 302 303 304
      # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
      # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. The reference column will be an +integer+
      # by default, the <tt>:type</tt> option can be used to specify a different type.
      #
      #  t.references(:user)
      #  t.references(:user, type: "string")
      #  t.belongs_to(:supplier, polymorphic: true)
305
      #
306
      # See SchemaStatements#add_reference
307 308 309
      def references(*args)
        options = args.extract_options!
        polymorphic = options.delete(:polymorphic)
310
        index_options = options.delete(:index)
311
        type = options.delete(:type) || :integer
312
        args.each do |col|
313
          column("#{col}_id", type, options)
314
          column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
315
          index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
316 317 318 319
        end
      end
      alias :belongs_to :references

320
      def new_column_definition(name, type, options) # :nodoc:
S
Sean Griffin 已提交
321
        type = aliased_types[type] || type
322 323 324 325 326
        column = create_column_definition name, type
        limit = options.fetch(:limit) do
          native[type][:limit] if native[type].is_a?(Hash)
        end

327
        column.limit       = limit
328
        column.array       = options[:array] if column.respond_to?(:array)
329 330 331 332 333 334 335
        column.precision   = options[:precision]
        column.scale       = options[:scale]
        column.default     = options[:default]
        column.null        = options[:null]
        column.first       = options[:first]
        column.after       = options[:after]
        column.primary_key = type == :primary_key || options[:primary_key]
336
        column
337 338
      end

339 340 341 342 343
      private
      def create_column_definition(name, type)
        ColumnDefinition.new name, type
      end

344
      def primary_key_column_name
A
Aaron Patterson 已提交
345
        primary_key_column = columns.detect { |c| c.primary_key? }
346 347 348
        primary_key_column && primary_key_column.name
      end

349
      def native
350
        @native
351
      end
S
Sean Griffin 已提交
352 353 354 355 356 357

      def aliased_types
        HashWithIndifferentAccess.new(
          timestamp: :datetime,
        )
      end
358
    end
359

360
    class AlterTable # :nodoc:
A
Aaron Patterson 已提交
361
      attr_reader :adds
362 363
      attr_reader :foreign_key_adds
      attr_reader :foreign_key_drops
364 365

      def initialize(td)
A
Aaron Patterson 已提交
366 367
        @td   = td
        @adds = []
368 369
        @foreign_key_adds = []
        @foreign_key_drops = []
370 371 372 373
      end

      def name; @td.name; end

374 375 376 377 378 379 380 381
      def add_foreign_key(to_table, options)
        @foreign_key_adds << ForeignKeyDefinition.new(name, to_table, options)
      end

      def drop_foreign_key(name)
        @foreign_key_drops << name
      end

382 383 384
      def add_column(name, type, options)
        name = name.to_s
        type = type.to_sym
A
Aaron Patterson 已提交
385
        @adds << @td.new_column_definition(name, type, options)
386 387 388
      end
    end

389
    # Represents an SQL table in an abstract way for updating a table.
390 391 392 393 394 395 396
    # Also see TableDefinition and SchemaStatements#create_table
    #
    # Available transformations are:
    #
    #   change_table :table do |t|
    #     t.column
    #     t.index
J
Jarek Radosz 已提交
397
    #     t.rename_index
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422
    #     t.timestamps
    #     t.change
    #     t.change_default
    #     t.rename
    #     t.references
    #     t.belongs_to
    #     t.string
    #     t.text
    #     t.integer
    #     t.float
    #     t.decimal
    #     t.datetime
    #     t.timestamp
    #     t.time
    #     t.date
    #     t.binary
    #     t.boolean
    #     t.remove
    #     t.remove_references
    #     t.remove_belongs_to
    #     t.remove_index
    #     t.remove_timestamps
    #   end
    #
    class Table
423 424
      include TimestampDefaultDeprecation

425 426 427 428 429 430 431
      def initialize(table_name, base)
        @table_name = table_name
        @base = base
      end

      # Adds a new column to the named table.
      # See TableDefinition#column for details of the options you can use.
432
      #
433
      # ====== Creating a simple column
434 435 436 437 438
      #  t.column(:name, :string)
      def column(column_name, type, options = {})
        @base.add_column(@table_name, column_name, type, options)
      end

439
      # Checks to see if a column exists. See SchemaStatements#column_exists?
440
      def column_exists?(column_name, type = nil, options = {})
441 442 443
        @base.column_exists?(@table_name, column_name, type, options)
      end

444 445
      # Adds a new index to the table. +column_name+ can be a single Symbol, or
      # an Array of Symbols. See SchemaStatements#add_index
446 447 448 449
      #
      # ====== Creating a simple index
      #  t.index(:name)
      # ====== Creating a unique index
A
AvnerCohen 已提交
450
      #  t.index([:branch_id, :party_id], unique: true)
451
      # ====== Creating a named index
A
AvnerCohen 已提交
452
      #  t.index([:branch_id, :party_id], unique: true, name: 'by_branch_party')
453 454 455 456
      def index(column_name, options = {})
        @base.add_index(@table_name, column_name, options)
      end

457 458 459 460 461
      # Checks to see if an index exists. See SchemaStatements#index_exists?
      def index_exists?(column_name, options = {})
        @base.index_exists?(@table_name, column_name, options)
      end

J
Jarek Radosz 已提交
462 463 464 465 466 467 468
      # Renames the given index on the table.
      #
      #  t.rename_index(:user_id, :account_id)
      def rename_index(index_name, new_index_name)
        @base.rename_index(@table_name, index_name, new_index_name)
      end

469
      # Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps
470
      #
471
      #  t.timestamps
472 473 474
      def timestamps(options = {})
        emit_warning_if_null_unspecified(options)
        @base.add_timestamps(@table_name, options)
475 476 477 478
      end

      # Changes the column's definition according to the new options.
      # See TableDefinition#column for details of the options you can use.
479
      #
A
AvnerCohen 已提交
480
      #  t.change(:name, :string, limit: 80)
481 482 483 484 485
      #  t.change(:description, :text)
      def change(column_name, type, options = {})
        @base.change_column(@table_name, column_name, type, options)
      end

486
      # Sets a new default value for a column. See SchemaStatements#change_column_default
487
      #
488 489 490 491 492 493 494
      #  t.change_default(:qualification, 'new')
      #  t.change_default(:authorized, 1)
      def change_default(column_name, default)
        @base.change_column_default(@table_name, column_name, default)
      end

      # Removes the column(s) from the table definition.
495
      #
496 497 498
      #  t.remove(:qualification)
      #  t.remove(:qualification, :experience)
      def remove(*column_names)
499
        @base.remove_columns(@table_name, *column_names)
500 501
      end

502
      # Removes the given index from the table.
503
      #
504 505 506
      # ====== Remove the index_table_name_on_column in the table_name table
      #   t.remove_index :column
      # ====== Remove the index named index_table_name_on_branch_id in the table_name table
A
AvnerCohen 已提交
507
      #   t.remove_index column: :branch_id
508
      # ====== Remove the index named index_table_name_on_branch_id_and_party_id in the table_name table
A
AvnerCohen 已提交
509
      #   t.remove_index column: [:branch_id, :party_id]
510
      # ====== Remove the index named by_branch_party in the table_name table
A
AvnerCohen 已提交
511
      #   t.remove_index name: :by_branch_party
512 513 514 515
      def remove_index(options = {})
        @base.remove_index(@table_name, options)
      end

516
      # Removes the timestamp columns (+created_at+ and +updated_at+) from the table.
517
      #
518 519 520 521 522 523
      #  t.remove_timestamps
      def remove_timestamps
        @base.remove_timestamps(@table_name)
      end

      # Renames a column.
524
      #
525 526 527 528 529
      #  t.rename(:description, :name)
      def rename(column_name, new_column_name)
        @base.rename_column(@table_name, column_name, new_column_name)
      end

530
      # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
531 532
      # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. The reference column will be an +integer+
      # by default, the <tt>:type</tt> option can be used to specify a different type.
533
      #
534
      #  t.references(:user)
535
      #  t.references(:user, type: "string")
536 537
      #  t.belongs_to(:supplier, polymorphic: true)
      #
538
      # See SchemaStatements#add_reference
539 540
      def references(*args)
        options = args.extract_options!
541 542
        args.each do |ref_name|
          @base.add_reference(@table_name, ref_name, options)
543 544 545 546
        end
      end
      alias :belongs_to :references

547 548
      # Removes a reference. Optionally removes a +type+ column.
      # <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
549
      #
550 551 552
      #  t.remove_references(:user)
      #  t.remove_belongs_to(:supplier, polymorphic: true)
      #
553
      # See SchemaStatements#remove_reference
554 555
      def remove_references(*args)
        options = args.extract_options!
556 557
        args.each do |ref_name|
          @base.remove_reference(@table_name, ref_name, options)
558 559
        end
      end
560
      alias :remove_belongs_to :remove_references
561 562

      # Adds a column or columns of a specified type
563
      #
564 565
      #  t.string(:goat)
      #  t.string(:goat, :sheep)
566 567 568 569 570 571
      [:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type|
        define_method column_type do |*args|
          options = args.extract_options!
          args.each do |name|
            @base.add_column(@table_name, name, column_type, options)
          end
572
        end
573 574 575 576 577 578 579
      end

      private
        def native
          @base.native_database_types
        end
    end
580
  end
581
end