schema_definitions.rb 21.4 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, :auto_increment, :primary_key, :sql_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
    class ChangeColumnDefinition < Struct.new(:column, :name) #:nodoc:
26 27
    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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
    class ReferenceDefinition # :nodoc:
      def initialize(
        name,
        polymorphic: false,
        index: false,
        foreign_key: false,
        type: :integer,
        **options
      )
        @name = name
        @polymorphic = polymorphic
        @index = index
        @foreign_key = foreign_key
        @type = type
        @options = options

        if polymorphic && foreign_key
          raise ArgumentError, "Cannot add a foreign key to a polymorphic relation"
        end
      end

      def add_to(table)
        columns.each do |column_options|
          table.column(*column_options)
        end

        if index
          table.index(column_names, index_options)
        end

        if foreign_key
          table.foreign_key(foreign_table_name, foreign_key_options)
        end
      end

      protected

      attr_reader :name, :polymorphic, :index, :foreign_key, :type, :options

      private

      def as_options(value, default = {})
        if value.is_a?(Hash)
          value
        else
          default
        end
      end

      def polymorphic_options
        as_options(polymorphic, options)
      end

      def index_options
        as_options(index)
      end

      def foreign_key_options
        as_options(foreign_key)
      end

      def columns
        result = [["#{name}_id", type, options]]
        if polymorphic
          result.unshift(["#{name}_type", :string, polymorphic_options])
        end
        result
      end

      def column_names
        columns.map(&:first)
      end

      def foreign_table_name
        name.to_s.pluralize
      end
    end

P
Pratik Naik 已提交
137 138 139
    # Represents the schema of an SQL table in an abstract way. This class
    # provides methods for manipulating the schema representation.
    #
140 141
    # Inside migration files, the +t+ object in +create_table+
    # is actually of this type:
P
Pratik Naik 已提交
142 143
    #
    #   class SomeMigration < ActiveRecord::Migration
A
Akira Matsuda 已提交
144
    #     def up
P
Pratik Naik 已提交
145 146 147 148
    #       create_table :foo do |t|
    #         puts t.class  # => "ActiveRecord::ConnectionAdapters::TableDefinition"
    #       end
    #     end
P
Pratik Naik 已提交
149
    #
A
Akira Matsuda 已提交
150
    #     def down
P
Pratik Naik 已提交
151 152 153 154 155 156
    #       ...
    #     end
    #   end
    #
    # The table definitions
    # The Columns are stored as a ColumnDefinition in the +columns+ attribute.
157
    class TableDefinition
P
Pratik Naik 已提交
158 159
      # An array of ColumnDefinition objects, representing the column changes
      # that have been defined.
160
      attr_accessor :indexes
161
      attr_reader :name, :temporary, :options, :as, :foreign_keys
162

163
      def initialize(types, name, temporary, options, as = nil)
164
        @columns_hash = {}
165
        @indexes = {}
166
        @foreign_keys = {}
167
        @native = types
168 169
        @temporary = temporary
        @options = options
170
        @as = as
171
        @name = name
172 173
      end

174 175
      def columns; @columns_hash.values; end

176 177
      # 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 已提交
178
      def primary_key(name, type = :primary_key, options = {})
179
        column(name, type, options.merge(:primary_key => true))
180
      end
181 182

      # Returns a ColumnDefinition for the column with name +name+.
183
      def [](name)
184
        @columns_hash[name.to_s]
185 186
      end

187
      # Instantiates a new column for the table.
188 189
      # The +type+ parameter is normally one of the migrations native types,
      # which is one of the following:
190
      # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
191
      # <tt>:integer</tt>, <tt>:float</tt>, <tt>:decimal</tt>,
192 193
      # <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>,
      # <tt>:binary</tt>, <tt>:boolean</tt>.
194
      #
195 196 197 198
      # 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.
      #
199
      # Available options are (none of these exists by default):
200
      # * <tt>:limit</tt> -
201
      #   Requests a maximum column length. This is number of characters for <tt>:string</tt> and
202
      #   <tt>:text</tt> columns and number of bytes for <tt>:binary</tt> and <tt>:integer</tt> columns.
203
      # * <tt>:default</tt> -
204
      #   The column's default value. Use nil for NULL.
205
      # * <tt>:null</tt> -
206
      #   Allows or disallows +NULL+ values in the column. This option could
207
      #   have been named <tt>:null_allowed</tt>.
208
      # * <tt>:precision</tt> -
209
      #   Specifies the precision for a <tt>:decimal</tt> column.
210
      # * <tt>:scale</tt> -
211
      #   Specifies the scale for a <tt>:decimal</tt> column.
212 213
      # * <tt>:index</tt> -
      #   Create an index for the column. Can be either <tt>true</tt> or an options hash.
214
      #
215 216
      # 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 已提交
217 218 219 220
      # 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.
      #
221 222 223 224 225
      # 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>.
226
      # * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
227
      #   Default is (10,0).
228
      # * PostgreSQL: <tt>:precision</tt> [1..infinity],
229
      #   <tt>:scale</tt> [0..infinity]. No default.
230
      # * SQLite2: Any <tt>:precision</tt> and <tt>:scale</tt> may be used.
231
      #   Internal storage as strings. No default.
232
      # * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
233
      #   but the maximum supported <tt>:precision</tt> is 16. No default.
234
      # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
235
      #   Default is (38,0).
236
      # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
237
      #   Default unknown.
238
      # * SqlServer?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
239
      #   Default (38,0).
240 241 242
      #
      # This method returns <tt>self</tt>.
      #
243
      # == Examples
244
      #  # Assuming +td+ is an instance of TableDefinition
245
      #  td.column(:granted, :boolean)
P
Pratik Naik 已提交
246
      #  # granted BOOLEAN
247
      #
A
AvnerCohen 已提交
248
      #  td.column(:picture, :binary, limit: 2.megabytes)
P
Pratik Naik 已提交
249
      #  # => picture BLOB(2097152)
250
      #
A
AvnerCohen 已提交
251
      #  td.column(:sales_stage, :string, limit: 20, default: 'new', null: false)
P
Pratik Naik 已提交
252
      #  # => sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL
253
      #
A
AvnerCohen 已提交
254
      #  td.column(:bill_gates_money, :decimal, precision: 15, scale: 2)
P
Pratik Naik 已提交
255
      #  # => bill_gates_money DECIMAL(15,2)
256
      #
A
AvnerCohen 已提交
257
      #  td.column(:sensor_reading, :decimal, precision: 30, scale: 20)
P
Pratik Naik 已提交
258
      #  # => sensor_reading DECIMAL(30,20)
259 260 261
      #
      #  # While <tt>:scale</tt> defaults to zero on most databases, it
      #  # probably wouldn't hurt to include it.
A
AvnerCohen 已提交
262
      #  td.column(:huge_integer, :decimal, precision: 30)
P
Pratik Naik 已提交
263
      #  # => huge_integer DECIMAL(30)
264
      #
P
Pratik Naik 已提交
265 266 267 268
      #  # Defines a column with a database-specific type.
      #  td.column(:foo, 'polygon')
      #  # => foo polygon
      #
269 270
      # == Short-hand examples
      #
P
Pratik Naik 已提交
271
      # Instead of calling +column+ directly, you can also work with the short-hand definitions for the default types.
272 273 274 275 276
      # 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:
      #
277
      #   create_table :products do |t|
278 279 280 281 282 283 284
      #     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
285
      #   end
286
      #   add_index :products, :item_number
287
      #
288
      # can also be written as follows using the short-hand:
289 290 291
      #
      #   create_table :products do |t|
      #     t.integer :shop_id, :creator_id
292
      #     t.string  :item_number, index: true
A
AvnerCohen 已提交
293
      #     t.string  :name, :value, default: "Untitled"
294
      #     t.timestamps null: false
295 296
      #   end
      #
297
      # There's a short-hand method for each of the type values declared at the top. And then there's
298
      # TableDefinition#timestamps that'll add +created_at+ and +updated_at+ as datetimes.
299 300
      #
      # TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type
301
      # column if the <tt>:polymorphic</tt> option is supplied. If <tt>:polymorphic</tt> is a hash of
302 303
      # 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:
304 305 306 307
      #
      #   create_table :taggings do |t|
      #     t.integer :tag_id, :tagger_id, :taggable_id
      #     t.string  :tagger_type
A
AvnerCohen 已提交
308
      #     t.string  :taggable_type, default: 'Photo'
309
      #   end
A
AvnerCohen 已提交
310
      #   add_index :taggings, :tag_id, name: 'index_taggings_on_tag_id'
311
      #   add_index :taggings, [:tagger_id, :tagger_type]
312 313 314 315
      #
      # Can also be written as follows using references:
      #
      #   create_table :taggings do |t|
A
AvnerCohen 已提交
316 317 318
      #     t.references :tag, index: { name: 'index_taggings_on_tag_id' }
      #     t.references :tagger, polymorphic: true, index: true
      #     t.references :taggable, polymorphic: { default: 'Photo' }
319
      #   end
320
      def column(name, type, options = {})
321 322 323
        name = name.to_s
        type = type.to_sym

324
        if @columns_hash[name] && @columns_hash[name].primary_key?
325 326 327
          raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table."
        end

328 329
        index_options = options.delete(:index)
        index(name, index_options.is_a?(Hash) ? index_options : {}) if index_options
330
        @columns_hash[name] = new_column_definition(name, type, options)
331 332
        self
      end
333

334 335 336 337
      def remove_column(name)
        @columns_hash.delete name.to_s
      end

A
Aaron Patterson 已提交
338
      [:string, :text, :integer, :bigint, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type|
339 340 341 342 343
        define_method column_type do |*args|
          options = args.extract_options!
          column_names = args
          column_names.each { |name| column(name, column_type, options) }
        end
344
      end
345

346 347
      # 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
348
      #
A
AvnerCohen 已提交
349
      #   index(:account_id, name: 'index_projects_on_account_id')
350 351 352
      def index(column_name, options = {})
        indexes[column_name] = options
      end
353

354 355 356 357
      def foreign_key(table_name, options = {}) # :nodoc:
        foreign_keys[table_name] = options
      end

358
      # Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
359 360 361
      # <tt>:updated_at</tt> to the table. See SchemaStatements#add_timestamps
      #
      #   t.timestamps null: false
362
      def timestamps(*args)
363
        options = args.extract_options!
364 365 366

        options[:null] = false if options[:null].nil?

367 368
        column(:created_at, :datetime, options)
        column(:updated_at, :datetime, options)
369 370
      end

R
Robin Dupret 已提交
371 372 373 374 375
      # Adds a reference. Optionally adds a +type+ column, if the
      # +:polymorphic+ option is provided. +references+ and +belongs_to+
      # are acceptable. The reference column will be an +integer+ by default,
      # the +:type+ option can be used to specify a different type. A foreign
      # key will be created if the +:foreign_key+ option is passed.
376 377 378 379
      #
      #  t.references(:user)
      #  t.references(:user, type: "string")
      #  t.belongs_to(:supplier, polymorphic: true)
380
      #
381
      # See SchemaStatements#add_reference
382
      def references(*args, **options)
383
        args.each do |col|
384
          ReferenceDefinition.new(col, **options).add_to(self)
385 386 387 388
        end
      end
      alias :belongs_to :references

389
      def new_column_definition(name, type, options) # :nodoc:
390
        type = aliased_types(type.to_s, type)
391 392 393 394 395
        column = create_column_definition name, type
        limit = options.fetch(:limit) do
          native[type][:limit] if native[type].is_a?(Hash)
        end

396 397 398 399 400 401 402
        column.limit       = limit
        column.precision   = options[:precision]
        column.scale       = options[:scale]
        column.default     = options[:default]
        column.null        = options[:null]
        column.first       = options[:first]
        column.after       = options[:after]
403
        column.auto_increment = options[:auto_increment]
404
        column.primary_key = type == :primary_key || options[:primary_key]
405
        column
406 407
      end

408 409 410 411 412
      private
      def create_column_definition(name, type)
        ColumnDefinition.new name, type
      end

413
      def native
414
        @native
415
      end
S
Sean Griffin 已提交
416

417 418
      def aliased_types(name, fallback)
        'timestamp' == name ? :datetime : fallback
S
Sean Griffin 已提交
419
      end
420
    end
421

422
    class AlterTable # :nodoc:
A
Aaron Patterson 已提交
423
      attr_reader :adds
424 425
      attr_reader :foreign_key_adds
      attr_reader :foreign_key_drops
426 427

      def initialize(td)
A
Aaron Patterson 已提交
428 429
        @td   = td
        @adds = []
430 431
        @foreign_key_adds = []
        @foreign_key_drops = []
432 433 434 435
      end

      def name; @td.name; end

436 437 438 439 440 441 442 443
      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

444 445 446
      def add_column(name, type, options)
        name = name.to_s
        type = type.to_sym
A
Aaron Patterson 已提交
447
        @adds << @td.new_column_definition(name, type, options)
448 449 450
      end
    end

451
    # Represents an SQL table in an abstract way for updating a table.
452 453 454 455 456 457 458
    # Also see TableDefinition and SchemaStatements#create_table
    #
    # Available transformations are:
    #
    #   change_table :table do |t|
    #     t.column
    #     t.index
J
Jarek Radosz 已提交
459
    #     t.rename_index
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484
    #     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
485 486
      attr_reader :name

487
      def initialize(table_name, base)
488
        @name = table_name
489 490 491 492
        @base = base
      end

      # Adds a new column to the named table.
493
      #
494
      #  t.column(:name, :string)
495 496
      #
      # See TableDefinition#column for details of the options you can use.
497
      def column(column_name, type, options = {})
498
        @base.add_column(name, column_name, type, options)
499 500
      end

501 502 503
      # Checks to see if a column exists.
      #
      # See SchemaStatements#column_exists?
504
      def column_exists?(column_name, type = nil, options = {})
505
        @base.column_exists?(name, column_name, type, options)
506 507
      end

508
      # Adds a new index to the table. +column_name+ can be a single Symbol, or
509
      # an Array of Symbols.
510 511
      #
      #  t.index(:name)
A
AvnerCohen 已提交
512 513
      #  t.index([:branch_id, :party_id], unique: true)
      #  t.index([:branch_id, :party_id], unique: true, name: 'by_branch_party')
514 515
      #
      # See SchemaStatements#add_index for details of the options you can use.
516
      def index(column_name, options = {})
517
        @base.add_index(name, column_name, options)
518 519
      end

520 521 522
      # Checks to see if an index exists.
      #
      # See SchemaStatements#index_exists?
523
      def index_exists?(column_name, options = {})
524
        @base.index_exists?(name, column_name, options)
525 526
      end

J
Jarek Radosz 已提交
527 528 529
      # Renames the given index on the table.
      #
      #  t.rename_index(:user_id, :account_id)
530 531
      #
      # See SchemaStatements#rename_index
J
Jarek Radosz 已提交
532
      def rename_index(index_name, new_index_name)
533
        @base.rename_index(name, index_name, new_index_name)
J
Jarek Radosz 已提交
534 535
      end

536 537 538
      # Adds timestamps (+created_at+ and +updated_at+) columns to the table.
      #
      #  t.timestamps(null: false)
539
      #
540
      # See SchemaStatements#add_timestamps
541
      def timestamps(options = {})
542
        @base.add_timestamps(name, options)
543 544 545
      end

      # Changes the column's definition according to the new options.
546
      #
A
AvnerCohen 已提交
547
      #  t.change(:name, :string, limit: 80)
548
      #  t.change(:description, :text)
549 550
      #
      # See TableDefinition#column for details of the options you can use.
551
      def change(column_name, type, options = {})
552
        @base.change_column(name, column_name, type, options)
553 554
      end

555
      # Sets a new default value for a column.
556
      #
557 558
      #  t.change_default(:qualification, 'new')
      #  t.change_default(:authorized, 1)
559 560
      #
      # See SchemaStatements#change_column_default
561
      def change_default(column_name, default)
562
        @base.change_column_default(name, column_name, default)
563 564 565
      end

      # Removes the column(s) from the table definition.
566
      #
567 568
      #  t.remove(:qualification)
      #  t.remove(:qualification, :experience)
569 570
      #
      # See SchemaStatements#remove_columns
571
      def remove(*column_names)
572
        @base.remove_columns(name, *column_names)
573 574
      end

575
      # Removes the given index from the table.
576
      #
577 578 579 580 581
      #   t.remove_index(:branch_id)
      #   t.remove_index(column: [:branch_id, :party_id])
      #   t.remove_index(name: :by_branch_party)
      #
      # See SchemaStatements#remove_index
582
      def remove_index(options = {})
583
        @base.remove_index(name, options)
584 585
      end

586
      # Removes the timestamp columns (+created_at+ and +updated_at+) from the table.
587
      #
588
      #  t.remove_timestamps
589 590
      #
      # See SchemaStatements#remove_timestamps
591 592
      def remove_timestamps(options = {})
        @base.remove_timestamps(name, options)
593 594 595
      end

      # Renames a column.
596
      #
597
      #  t.rename(:description, :name)
598 599
      #
      # See SchemaStatements#rename_column
600
      def rename(column_name, new_column_name)
601
        @base.rename_column(name, column_name, new_column_name)
602 603
      end

604
      # Adds a reference. Optionally adds a +type+ column, if
605
      # <tt>:polymorphic</tt> option is provided.
606
      #
607
      #  t.references(:user)
608
      #  t.references(:user, type: "string")
609
      #  t.belongs_to(:supplier, polymorphic: true)
610
      #  t.belongs_to(:supplier, foreign_key: true)
611
      #
612
      # See SchemaStatements#add_reference
613 614
      def references(*args)
        options = args.extract_options!
615
        args.each do |ref_name|
616
          @base.add_reference(name, ref_name, options)
617 618 619 620
        end
      end
      alias :belongs_to :references

621
      # Removes a reference. Optionally removes a +type+ column.
622
      #
623 624 625
      #  t.remove_references(:user)
      #  t.remove_belongs_to(:supplier, polymorphic: true)
      #
626
      # See SchemaStatements#remove_reference
627 628
      def remove_references(*args)
        options = args.extract_options!
629
        args.each do |ref_name|
630
          @base.remove_reference(name, ref_name, options)
631 632
        end
      end
633
      alias :remove_belongs_to :remove_references
634

635
      # Adds a column or columns of a specified type.
636
      #
637 638
      #  t.string(:goat)
      #  t.string(:goat, :sheep)
639 640
      #
      # See SchemaStatements#add_column
641 642 643
      [: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!
644 645
          args.each do |column_name|
            @base.add_column(name, column_name, column_type, options)
646
          end
647
        end
648 649
      end

650 651 652 653
      def foreign_key(*args) # :nodoc:
        @base.add_foreign_key(name, *args)
      end

654 655 656 657 658
      private
        def native
          @base.native_database_types
        end
    end
659
  end
660
end