schema_definitions.rb 19.8 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
    class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths) #:nodoc:
10 11
    end

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

18 19 20 21
      def string_to_binary(value)
        value
      end

22 23 24
      def sql_type
        base.type_to_sql(type.to_sym, limit, precision, scale) rescue type
      end
25

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

35 36 37 38 39 40 41
      private

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

P
Pratik Naik 已提交
42 43 44 45 46 47 48 49 50 51 52 53
    # 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
    #     def self.up
    #       create_table :foo do |t|
    #         puts t.class  # => "ActiveRecord::ConnectionAdapters::TableDefinition"
    #       end
    #     end
P
Pratik Naik 已提交
54
    #
P
Pratik Naik 已提交
55 56 57 58 59 60 61
    #     def self.down
    #       ...
    #     end
    #   end
    #
    # The table definitions
    # The Columns are stored as a ColumnDefinition in the +columns+ attribute.
62
    class TableDefinition
P
Pratik Naik 已提交
63 64
      # An array of ColumnDefinition objects, representing the column changes
      # that have been defined.
65 66 67 68 69 70 71
      attr_accessor :columns

      def initialize(base)
        @columns = []
        @base = base
      end

72 73 74 75
      def xml(*args)
        raise NotImplementedError unless %w{
          sqlite mysql mysql2
        }.include? @base.adapter_name.downcase
76

77 78
        options = args.extract_options!
        column(args[0], :text, options)
79 80
      end

81 82
      # Appends a primary key definition to the table definition.
      # Can be called multiple times, but this is probably not a good idea.
83 84
      def primary_key(name)
        column(name, :primary_key)
85
      end
86 87

      # Returns a ColumnDefinition for the column with name +name+.
88
      def [](name)
89
        @columns.find {|column| column.name.to_s == name.to_s}
90 91
      end

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

241
      %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
242
        class_eval <<-EOV, __FILE__, __LINE__ + 1
243 244 245 246 247 248
          def #{column_type}(*args)                                               # def string(*args)
            options = args.extract_options!                                       #   options = args.extract_options!
            column_names = args                                                   #   column_names = args
                                                                                  #
            column_names.each { |name| column(name, '#{column_type}', options) }  #   column_names.each { |name| column(name, 'string', options) }
          end                                                                     # end
249 250
        EOV
      end
251 252

      # Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
253
      # <tt>:updated_at</tt> to the table.
254 255 256 257
      def timestamps(*args)
        options = args.extract_options!
        column(:created_at, :datetime, options)
        column(:updated_at, :datetime, options)
258 259
      end

260 261 262 263 264
      def references(*args)
        options = args.extract_options!
        polymorphic = options.delete(:polymorphic)
        args.each do |col|
          column("#{col}_id", :integer, options)
265
          column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil?
266 267 268 269
        end
      end
      alias :belongs_to :references

270
      # Returns a String whose contents are the column definitions
271
      # concatenated together. This string can then be prepended and appended to
272
      # to generate the final SQL to create the table.
273
      def to_sql
274
        @columns.map { |c| c.to_sql } * ', '
275
      end
276

277 278 279 280 281
      private
        def native
          @base.native_database_types
        end
    end
282

283
    # Represents an SQL table in an abstract way for updating a table.
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
    # 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.
323 324
      # ===== Example
      # ====== Creating a simple column
325 326 327 328 329
      #  t.column(:name, :string)
      def column(column_name, type, options = {})
        @base.add_column(@table_name, column_name, type, options)
      end

330 331 332 333 334
      # Checks to see if a column exists. See SchemaStatements#column_exists?
      def column_exists?(column_name, type = nil, options = nil)
        @base.column_exists?(@table_name, column_name, type, options)
      end

335 336
      # Adds a new index to the table. +column_name+ can be a single Symbol, or
      # an Array of Symbols. See SchemaStatements#add_index
337 338 339 340 341 342 343 344 345 346 347 348
      #
      # ===== 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

349 350 351 352 353
      # 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

354
      # Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps
355
      # ===== Example
356 357 358 359 360 361 362 363 364 365 366 367 368 369
      #  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

370
      # Sets a new default value for a column. See SchemaStatements#change_column_default
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
      # ===== 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)
        @base.remove_column(@table_name, column_names)
      end

386
      # Removes the given index from the table.
387
      #
388
      # ===== Examples
389 390 391
      # ====== 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
392
      #   t.remove_index :column => :branch_id
393
      # ====== Remove the index named index_table_name_on_branch_id_and_party_id in the table_name table
394
      #   t.remove_index :column => [:branch_id, :party_id]
395
      # ====== Remove the index named by_branch_party in the table_name table
396 397 398 399 400
      #   t.remove_index :name => :by_branch_party
      def remove_index(options = {})
        @base.remove_index(@table_name, options)
      end

401
      # Removes the timestamp columns (+created_at+ and +updated_at+) from the table.
402
      # ===== Example
403 404 405 406 407 408 409 410 411 412 413 414
      #  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

415
      # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
416 417
      # <tt>references</tt> and <tt>belongs_to</tt> are acceptable.
      # ===== Examples
418 419 420 421 422 423 424 425 426 427 428 429 430
      #  t.references(:goat)
      #  t.references(:goat, :polymorphic => true)
      #  t.belongs_to(:goat)
      def references(*args)
        options = args.extract_options!
        polymorphic = options.delete(:polymorphic)
        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?
        end
      end
      alias :belongs_to :references

431 432 433
      # Removes a reference. Optionally removes a +type+ column.
      # <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
      # ===== Examples
434
      #  t.remove_references(:goat)
435
      #  t.remove_references(:goat, :polymorphic => true)
436 437 438 439 440 441 442 443 444 445 446 447
      #  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
448
      # ===== Examples
449 450 451
      #  t.string(:goat)
      #  t.string(:goat, :sheep)
      %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
452
        class_eval <<-EOV, __FILE__, __LINE__ + 1
453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
          def #{column_type}(*args)                                          # def string(*args)
            options = args.extract_options!                                  #   options = args.extract_options!
            column_names = args                                              #   column_names = args
                                                                             #
            column_names.each do |name|                                      #   column_names.each do |name|
              column = ColumnDefinition.new(@base, name, '#{column_type}')   #     column = ColumnDefinition.new(@base, name, 'string')
              if options[:limit]                                             #     if options[:limit]
                column.limit = options[:limit]                               #       column.limit = options[:limit]
              elsif native['#{column_type}'.to_sym].is_a?(Hash)              #     elsif native['string'.to_sym].is_a?(Hash)
                column.limit = native['#{column_type}'.to_sym][:limit]       #       column.limit = native['string'.to_sym][:limit]
              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
471 472 473 474 475 476 477 478 479
        EOV
      end

      private
        def native
          @base.native_database_types
        end
    end

480
  end
481
end
482