schema_definitions.rb 18.1 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) #: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

P
Pratik Naik 已提交
28 29 30
    # Represents the schema of an SQL table in an abstract way. This class
    # provides methods for manipulating the schema representation.
    #
31 32
    # Inside migration files, the +t+ object in +create_table+
    # is actually of this type:
P
Pratik Naik 已提交
33 34
    #
    #   class SomeMigration < ActiveRecord::Migration
A
Akira Matsuda 已提交
35
    #     def up
P
Pratik Naik 已提交
36 37 38 39
    #       create_table :foo do |t|
    #         puts t.class  # => "ActiveRecord::ConnectionAdapters::TableDefinition"
    #       end
    #     end
P
Pratik Naik 已提交
40
    #
A
Akira Matsuda 已提交
41
    #     def down
P
Pratik Naik 已提交
42 43 44 45 46 47
    #       ...
    #     end
    #   end
    #
    # The table definitions
    # The Columns are stored as a ColumnDefinition in the +columns+ attribute.
48
    class TableDefinition
P
Pratik Naik 已提交
49 50
      # An array of ColumnDefinition objects, representing the column changes
      # that have been defined.
51
      attr_accessor :indexes
52
      attr_reader :name, :temporary, :options, :as
53

54
      def initialize(types, name, temporary, options, as = nil)
55
        @columns_hash = {}
56
        @indexes = {}
57
        @native = types
58 59
        @temporary = temporary
        @options = options
60
        @as = as
61
        @name = name
62 63
      end

64 65
      def columns; @columns_hash.values; end

66 67
      # 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 已提交
68
      def primary_key(name, type = :primary_key, options = {})
69
        column(name, type, options.merge(:primary_key => true))
70
      end
71 72

      # Returns a ColumnDefinition for the column with name +name+.
73
      def [](name)
74
        @columns_hash[name.to_s]
75 76
      end

77
      # Instantiates a new column for the table.
78 79
      # The +type+ parameter is normally one of the migrations native types,
      # which is one of the following:
80
      # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
81 82 83
      # <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>.
84
      #
85 86 87 88
      # 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.
      #
89
      # Available options are (none of these exists by default):
90
      # * <tt>:limit</tt> -
91
      #   Requests a maximum column length. This is number of characters for <tt>:string</tt> and
92
      #   <tt>:text</tt> columns and number of bytes for <tt>:binary</tt> and <tt>:integer</tt> columns.
93
      # * <tt>:default</tt> -
94
      #   The column's default value. Use nil for NULL.
95
      # * <tt>:null</tt> -
96
      #   Allows or disallows +NULL+ values in the column. This option could
97
      #   have been named <tt>:null_allowed</tt>.
98
      # * <tt>:precision</tt> -
99
      #   Specifies the precision for a <tt>:decimal</tt> column.
100
      # * <tt>:scale</tt> -
101
      #   Specifies the scale for a <tt>:decimal</tt> column.
102
      #
P
Pratik Naik 已提交
103 104 105 106 107 108
      # For clarity's sake: the precision is the number of significant digits,
      # while the scale is the number of digits that can be stored following
      # 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.
      #
109 110 111 112 113
      # 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>.
114
      # * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
115
      #   Default is (10,0).
116
      # * PostgreSQL: <tt>:precision</tt> [1..infinity],
117
      #   <tt>:scale</tt> [0..infinity]. No default.
118
      # * SQLite2: Any <tt>:precision</tt> and <tt>:scale</tt> may be used.
119
      #   Internal storage as strings. No default.
120
      # * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
121
      #   but the maximum supported <tt>:precision</tt> is 16. No default.
122
      # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
123
      #   Default is (38,0).
124
      # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
125
      #   Default unknown.
126
      # * SqlServer?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
127
      #   Default (38,0).
128 129 130
      #
      # This method returns <tt>self</tt>.
      #
131
      # == Examples
132
      #  # Assuming +td+ is an instance of TableDefinition
133
      #  td.column(:granted, :boolean)
P
Pratik Naik 已提交
134
      #  # granted BOOLEAN
135
      #
A
AvnerCohen 已提交
136
      #  td.column(:picture, :binary, limit: 2.megabytes)
P
Pratik Naik 已提交
137
      #  # => picture BLOB(2097152)
138
      #
A
AvnerCohen 已提交
139
      #  td.column(:sales_stage, :string, limit: 20, default: 'new', null: false)
P
Pratik Naik 已提交
140
      #  # => sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL
141
      #
A
AvnerCohen 已提交
142
      #  td.column(:bill_gates_money, :decimal, precision: 15, scale: 2)
P
Pratik Naik 已提交
143
      #  # => bill_gates_money DECIMAL(15,2)
144
      #
A
AvnerCohen 已提交
145
      #  td.column(:sensor_reading, :decimal, precision: 30, scale: 20)
P
Pratik Naik 已提交
146
      #  # => sensor_reading DECIMAL(30,20)
147 148 149
      #
      #  # While <tt>:scale</tt> defaults to zero on most databases, it
      #  # probably wouldn't hurt to include it.
A
AvnerCohen 已提交
150
      #  td.column(:huge_integer, :decimal, precision: 30)
P
Pratik Naik 已提交
151
      #  # => huge_integer DECIMAL(30)
152
      #
P
Pratik Naik 已提交
153 154 155 156
      #  # Defines a column with a database-specific type.
      #  td.column(:foo, 'polygon')
      #  # => foo polygon
      #
157 158
      # == Short-hand examples
      #
P
Pratik Naik 已提交
159
      # Instead of calling +column+ directly, you can also work with the short-hand definitions for the default types.
160 161 162 163 164
      # 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:
      #
165 166 167 168 169 170 171
      #   create_table :products do |t|
      #     t.column :shop_id,    :integer
      #     t.column :creator_id, :integer
      #     t.column :name,       :string, default: "Untitled"
      #     t.column :value,      :string, default: "Untitled"
      #     t.column :created_at, :datetime
      #     t.column :updated_at, :datetime
172 173
      #   end
      #
174
      # can also be written as follows using the short-hand:
175 176 177
      #
      #   create_table :products do |t|
      #     t.integer :shop_id, :creator_id
A
AvnerCohen 已提交
178
      #     t.string  :name, :value, default: "Untitled"
179 180 181
      #     t.timestamps
      #   end
      #
182
      # There's a short-hand method for each of the type values declared at the top. And then there's
183
      # TableDefinition#timestamps that'll add +created_at+ and +updated_at+ as datetimes.
184 185
      #
      # TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type
186
      # column if the <tt>:polymorphic</tt> option is supplied. If <tt>:polymorphic</tt> is a hash of
187 188
      # 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:
189 190 191 192
      #
      #   create_table :taggings do |t|
      #     t.integer :tag_id, :tagger_id, :taggable_id
      #     t.string  :tagger_type
A
AvnerCohen 已提交
193
      #     t.string  :taggable_type, default: 'Photo'
194
      #   end
A
AvnerCohen 已提交
195
      #   add_index :taggings, :tag_id, name: 'index_taggings_on_tag_id'
196
      #   add_index :taggings, [:tagger_id, :tagger_type]
197 198 199 200
      #
      # Can also be written as follows using references:
      #
      #   create_table :taggings do |t|
A
AvnerCohen 已提交
201 202 203
      #     t.references :tag, index: { name: 'index_taggings_on_tag_id' }
      #     t.references :tagger, polymorphic: true, index: true
      #     t.references :taggable, polymorphic: { default: 'Photo' }
204
      #   end
205
      def column(name, type, options = {})
206 207 208
        name = name.to_s
        type = type.to_sym

209 210 211 212
        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

213
        @columns_hash[name] = new_column_definition(name, type, options)
214 215
        self
      end
216

217 218 219 220
      def remove_column(name)
        @columns_hash.delete name.to_s
      end

221 222 223 224 225 226
      [: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
227
      end
228

229 230
      # 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
231
      #
A
AvnerCohen 已提交
232
      #   index(:account_id, name: 'index_projects_on_account_id')
233 234 235
      def index(column_name, options = {})
        indexes[column_name] = options
      end
236 237

      # Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
238
      # <tt>:updated_at</tt> to the table.
239
      def timestamps(*args)
240
        options = args.extract_options!
241 242
        column(:created_at, :datetime, options)
        column(:updated_at, :datetime, options)
243 244
      end

245 246 247
      def references(*args)
        options = args.extract_options!
        polymorphic = options.delete(:polymorphic)
248
        index_options = options.delete(:index)
249 250
        args.each do |col|
          column("#{col}_id", :integer, options)
251
          column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
252
          index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
253 254 255 256
        end
      end
      alias :belongs_to :references

257
      def new_column_definition(name, type, options) # :nodoc:
258 259 260 261 262
        column = create_column_definition name, type
        limit = options.fetch(:limit) do
          native[type][:limit] if native[type].is_a?(Hash)
        end

263
        column.limit       = limit
264
        column.array       = options[:array] if column.respond_to?(:array)
265 266 267 268 269 270 271
        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]
272
        column
273 274
      end

275 276 277 278 279
      private
      def create_column_definition(name, type)
        ColumnDefinition.new name, type
      end

280
      def primary_key_column_name
A
Aaron Patterson 已提交
281
        primary_key_column = columns.detect { |c| c.primary_key? }
282 283 284
        primary_key_column && primary_key_column.name
      end

285
      def native
286
        @native
287
      end
288
    end
289

290
    class AlterTable # :nodoc:
A
Aaron Patterson 已提交
291
      attr_reader :adds
292 293

      def initialize(td)
A
Aaron Patterson 已提交
294 295
        @td   = td
        @adds = []
296 297 298 299 300 301 302
      end

      def name; @td.name; end

      def add_column(name, type, options)
        name = name.to_s
        type = type.to_sym
A
Aaron Patterson 已提交
303
        @adds << @td.new_column_definition(name, type, options)
304 305 306
      end
    end

307
    # Represents an SQL table in an abstract way for updating a table.
308 309 310 311 312 313 314
    # Also see TableDefinition and SchemaStatements#create_table
    #
    # Available transformations are:
    #
    #   change_table :table do |t|
    #     t.column
    #     t.index
J
Jarek Radosz 已提交
315
    #     t.rename_index
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
    #     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
      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.
348
      #
349
      # ====== Creating a simple column
350 351 352 353 354
      #  t.column(:name, :string)
      def column(column_name, type, options = {})
        @base.add_column(@table_name, column_name, type, options)
      end

355
      # Checks to see if a column exists. See SchemaStatements#column_exists?
356
      def column_exists?(column_name, type = nil, options = {})
357 358 359
        @base.column_exists?(@table_name, column_name, type, options)
      end

360 361
      # Adds a new index to the table. +column_name+ can be a single Symbol, or
      # an Array of Symbols. See SchemaStatements#add_index
362 363 364 365
      #
      # ====== Creating a simple index
      #  t.index(:name)
      # ====== Creating a unique index
A
AvnerCohen 已提交
366
      #  t.index([:branch_id, :party_id], unique: true)
367
      # ====== Creating a named index
A
AvnerCohen 已提交
368
      #  t.index([:branch_id, :party_id], unique: true, name: 'by_branch_party')
369 370 371 372
      def index(column_name, options = {})
        @base.add_index(@table_name, column_name, options)
      end

373 374 375 376 377
      # 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 已提交
378 379 380 381 382 383 384
      # 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

385
      # Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps
386
      #
387 388 389 390 391 392 393
      #  t.timestamps
      def timestamps
        @base.add_timestamps(@table_name)
      end

      # Changes the column's definition according to the new options.
      # See TableDefinition#column for details of the options you can use.
394
      #
A
AvnerCohen 已提交
395
      #  t.change(:name, :string, limit: 80)
396 397 398 399 400
      #  t.change(:description, :text)
      def change(column_name, type, options = {})
        @base.change_column(@table_name, column_name, type, options)
      end

401
      # Sets a new default value for a column. See SchemaStatements#change_column_default
402
      #
403 404 405 406 407 408 409
      #  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.
410
      #
411 412 413
      #  t.remove(:qualification)
      #  t.remove(:qualification, :experience)
      def remove(*column_names)
414
        @base.remove_columns(@table_name, *column_names)
415 416
      end

417
      # Removes the given index from the table.
418
      #
419 420 421
      # ====== 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 已提交
422
      #   t.remove_index column: :branch_id
423
      # ====== Remove the index named index_table_name_on_branch_id_and_party_id in the table_name table
A
AvnerCohen 已提交
424
      #   t.remove_index column: [:branch_id, :party_id]
425
      # ====== Remove the index named by_branch_party in the table_name table
A
AvnerCohen 已提交
426
      #   t.remove_index name: :by_branch_party
427 428 429 430
      def remove_index(options = {})
        @base.remove_index(@table_name, options)
      end

431
      # Removes the timestamp columns (+created_at+ and +updated_at+) from the table.
432
      #
433 434 435 436 437 438
      #  t.remove_timestamps
      def remove_timestamps
        @base.remove_timestamps(@table_name)
      end

      # Renames a column.
439
      #
440 441 442 443 444
      #  t.rename(:description, :name)
      def rename(column_name, new_column_name)
        @base.rename_column(@table_name, column_name, new_column_name)
      end

445
      # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
446
      # <tt>references</tt> and <tt>belongs_to</tt> are acceptable.
447
      #
448 449 450
      #  t.references(:user)
      #  t.belongs_to(:supplier, polymorphic: true)
      #
451 452
      def references(*args)
        options = args.extract_options!
453 454
        args.each do |ref_name|
          @base.add_reference(@table_name, ref_name, options)
455 456 457 458
        end
      end
      alias :belongs_to :references

459 460
      # Removes a reference. Optionally removes a +type+ column.
      # <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
461
      #
462 463 464
      #  t.remove_references(:user)
      #  t.remove_belongs_to(:supplier, polymorphic: true)
      #
465 466
      def remove_references(*args)
        options = args.extract_options!
467 468
        args.each do |ref_name|
          @base.remove_reference(@table_name, ref_name, options)
469 470
        end
      end
471
      alias :remove_belongs_to :remove_references
472 473

      # Adds a column or columns of a specified type
474
      #
475 476
      #  t.string(:goat)
      #  t.string(:goat, :sheep)
477 478 479 480 481 482
      [: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
483
        end
484 485 486 487 488 489 490 491
      end

      private
        def native
          @base.native_database_types
        end
    end

492
  end
493
end