schema_definitions.rb 21.2 KB
Newer Older
1
require 'active_support/core_ext/object/blank'
2
require 'date'
3
require 'set'
4 5
require 'bigdecimal'
require 'bigdecimal/util'
6

7 8
module ActiveRecord
  module ConnectionAdapters #:nodoc:
9 10 11
    # 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
12
    class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where) #:nodoc:
13 14
    end

P
Pratik Naik 已提交
15 16 17 18
    # 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.
19
    class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :precision, :scale, :default, :null) #:nodoc:
20

21 22 23 24
      def string_to_binary(value)
        value
      end

25 26 27
      def sql_type
        base.type_to_sql(type.to_sym, limit, precision, scale) rescue type
      end
28

29
      def to_sql
30
        column_sql = "#{base.quote_column_name(name)} #{sql_type}"
31 32 33
        column_options = {}
        column_options[:null] = null unless null.nil?
        column_options[:default] = default unless default.nil?
34
        add_column_options!(column_sql, column_options) unless type.to_sym == :primary_key
35 36
        column_sql
      end
37

38 39 40 41 42 43 44
      private

        def add_column_options!(sql, options)
          base.add_column_options!(sql, options.merge(:column => self))
        end
    end

P
Pratik Naik 已提交
45 46 47 48 49 50 51
    # Represents the schema of an SQL table in an abstract way. This class
    # provides methods for manipulating the schema representation.
    #
    # Inside migration files, the +t+ object in +create_table+ and
    # +change_table+ is actually of this type:
    #
    #   class SomeMigration < ActiveRecord::Migration
A
Akira Matsuda 已提交
52
    #     def up
P
Pratik Naik 已提交
53 54 55 56
    #       create_table :foo do |t|
    #         puts t.class  # => "ActiveRecord::ConnectionAdapters::TableDefinition"
    #       end
    #     end
P
Pratik Naik 已提交
57
    #
A
Akira Matsuda 已提交
58
    #     def down
P
Pratik Naik 已提交
59 60 61 62 63 64
    #       ...
    #     end
    #   end
    #
    # The table definitions
    # The Columns are stored as a ColumnDefinition in the +columns+ attribute.
65
    class TableDefinition
P
Pratik Naik 已提交
66 67
      # An array of ColumnDefinition objects, representing the column changes
      # that have been defined.
68
      attr_accessor :columns, :indexes
69 70 71

      def initialize(base)
        @columns = []
72
        @columns_hash = {}
73
        @indexes = {}
74 75 76
        @base = base
      end

77 78 79 80
      def xml(*args)
        raise NotImplementedError unless %w{
          sqlite mysql mysql2
        }.include? @base.adapter_name.downcase
81

82 83
        options = args.extract_options!
        column(args[0], :text, options)
84 85
      end

86 87
      # Appends a primary key definition to the table definition.
      # Can be called multiple times, but this is probably not a good idea.
88 89
      def primary_key(name)
        column(name, :primary_key)
90
      end
91 92

      # Returns a ColumnDefinition for the column with name +name+.
93
      def [](name)
94
        @columns_hash[name.to_s]
95 96
      end

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

        column = self[name] || new_column_definition(@base, name, type)

        limit = options.fetch(:limit) do
          native[type][:limit] if native[type].is_a?(Hash)
242
        end
243 244

        column.limit     = limit
245
        column.precision = options[:precision]
246 247 248
        column.scale     = options[:scale]
        column.default   = options[:default]
        column.null      = options[:null]
249 250
        self
      end
251

252
      %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
253
        class_eval <<-EOV, __FILE__, __LINE__ + 1
254 255 256
          def #{column_type}(*args)                                   # def string(*args)
            options = args.extract_options!                           #   options = args.extract_options!
            column_names = args                                       #   column_names = args
V
Vijay Dev 已提交
257
            type = :'#{column_type}'                                  #   type = :string
258 259
            column_names.each { |name| column(name, type, options) }  #   column_names.each { |name| column(name, type, options) }
          end                                                         # end
260 261
        EOV
      end
262 263 264 265 266 267 268 269
      
      # 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
      # === Examples
      #   index(:account_id, :name => 'index_projects_on_account_id')
      def index(column_name, options = {})
        indexes[column_name] = options
      end
270 271

      # Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
272
      # <tt>:updated_at</tt> to the table.
273
      def timestamps(*args)
M
Mike Perham 已提交
274
        options = { :null => false }.merge(args.extract_options!)
275 276
        column(:created_at, :datetime, options)
        column(:updated_at, :datetime, options)
277 278
      end

279 280 281
      def references(*args)
        options = args.extract_options!
        polymorphic = options.delete(:polymorphic)
282
        index_options = options.delete(:index)
283 284
        args.each do |col|
          column("#{col}_id", :integer, options)
285
          column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil?
286
          index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
287 288 289 290
        end
      end
      alias :belongs_to :references

291
      # Returns a String whose contents are the column definitions
292
      # concatenated together. This string can then be prepended and appended to
293
      # to generate the final SQL to create the table.
294
      def to_sql
295
        @columns.map { |c| c.to_sql } * ', '
296
      end
297

298
      private
299 300 301
      def new_column_definition(base, name, type)
        definition = ColumnDefinition.new base, name, type
        @columns << definition
302
        @columns_hash[name] = definition
303 304 305 306 307 308
        definition
      end

      def native
        @base.native_database_types
      end
309
    end
310

311
    # Represents an SQL table in an abstract way for updating a table.
312 313 314 315 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 348 349 350
    # Also see TableDefinition and SchemaStatements#create_table
    #
    # Available transformations are:
    #
    #   change_table :table do |t|
    #     t.column
    #     t.index
    #     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.
351 352
      # ===== Example
      # ====== Creating a simple column
353 354 355 356 357
      #  t.column(:name, :string)
      def column(column_name, type, options = {})
        @base.add_column(@table_name, column_name, type, options)
      end

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

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

377 378 379 380 381
      # 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

382
      # Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps
383
      # ===== Example
384 385 386 387 388 389 390 391 392 393 394 395 396 397
      #  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.
      # ===== Examples
      #  t.change(:name, :string, :limit => 80)
      #  t.change(:description, :text)
      def change(column_name, type, options = {})
        @base.change_column(@table_name, column_name, type, options)
      end

398
      # Sets a new default value for a column. See SchemaStatements#change_column_default
399 400 401 402 403 404 405 406 407 408 409 410
      # ===== Examples
      #  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.
      # ===== Examples
      #  t.remove(:qualification)
      #  t.remove(:qualification, :experience)
      def remove(*column_names)
E
Edgars Beigarts 已提交
411
        @base.remove_column(@table_name, *column_names)
412 413
      end

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

429
      # Removes the timestamp columns (+created_at+ and +updated_at+) from the table.
430
      # ===== Example
431 432 433 434 435 436 437 438 439 440 441 442
      #  t.remove_timestamps
      def remove_timestamps
        @base.remove_timestamps(@table_name)
      end

      # Renames a column.
      # ===== Example
      #  t.rename(:description, :name)
      def rename(column_name, new_column_name)
        @base.rename_column(@table_name, column_name, new_column_name)
      end

443
      # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
444 445
      # <tt>references</tt> and <tt>belongs_to</tt> are acceptable.
      # ===== Examples
446 447 448 449 450 451
      #  t.references(:goat)
      #  t.references(:goat, :polymorphic => true)
      #  t.belongs_to(:goat)
      def references(*args)
        options = args.extract_options!
        polymorphic = options.delete(:polymorphic)
452
        index_options = options.delete(:index)
453 454 455
        args.each do |col|
          @base.add_column(@table_name, "#{col}_id", :integer, options)
          @base.add_column(@table_name, "#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil?
456
          @base.add_index(@table_name, polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
457 458 459 460
        end
      end
      alias :belongs_to :references

461 462 463
      # Removes a reference. Optionally removes a +type+ column.
      # <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
      # ===== Examples
464
      #  t.remove_references(:goat)
465
      #  t.remove_references(:goat, :polymorphic => true)
466 467 468 469 470 471 472 473 474 475 476 477
      #  t.remove_belongs_to(:goat)
      def remove_references(*args)
        options = args.extract_options!
        polymorphic = options.delete(:polymorphic)
        args.each do |col|
          @base.remove_column(@table_name, "#{col}_id")
          @base.remove_column(@table_name, "#{col}_type") unless polymorphic.nil?
        end
      end
      alias :remove_belongs_to  :remove_references

      # Adds a column or columns of a specified type
478
      # ===== Examples
479 480 481
      #  t.string(:goat)
      #  t.string(:goat, :sheep)
      %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
482
        class_eval <<-EOV, __FILE__, __LINE__ + 1
483 484 485
          def #{column_type}(*args)                                          # def string(*args)
            options = args.extract_options!                                  #   options = args.extract_options!
            column_names = args                                              #   column_names = args
V
Vijay Dev 已提交
486
            type = :'#{column_type}'                                         #   type = :string
487
            column_names.each do |name|                                      #   column_names.each do |name|
488
              column = ColumnDefinition.new(@base, name.to_s, type)          #     column = ColumnDefinition.new(@base, name, type)
489 490
              if options[:limit]                                             #     if options[:limit]
                column.limit = options[:limit]                               #       column.limit = options[:limit]
491 492
              elsif native[type].is_a?(Hash)                                 #     elsif native[type].is_a?(Hash)
                column.limit = native[type][:limit]                          #       column.limit = native[type][:limit]
493 494 495 496 497 498 499 500
              end                                                            #     end
              column.precision = options[:precision]                         #     column.precision = options[:precision]
              column.scale = options[:scale]                                 #     column.scale = options[:scale]
              column.default = options[:default]                             #     column.default = options[:default]
              column.null = options[:null]                                   #     column.null = options[:null]
              @base.add_column(@table_name, name, column.sql_type, options)  #     @base.add_column(@table_name, name, column.sql_type, options)
            end                                                              #   end
          end                                                                # end
501 502 503 504 505 506 507 508 509
        EOV
      end

      private
        def native
          @base.native_database_types
        end
    end

510
  end
511
end