schema_statements.rb 17.3 KB
Newer Older
1 2
require 'active_support/core_ext/array/wrap'

3 4 5
module ActiveRecord
  module ConnectionAdapters # :nodoc:
    module SchemaStatements
6 7 8 9
      # Returns a Hash of mappings from the abstract data types to the native
      # database types.  See TableDefinition#column for details on the recognized
      # abstract data types.
      def native_database_types
10 11 12
        {}
      end

13 14 15 16 17
      # This is the maximum length a table alias can be
      def table_alias_length
        255
      end

18
      # Truncates a table alias according to the limits of the current adapter.
19
      def table_alias_for(table_name)
20
        table_name[0..table_alias_length-1].gsub(/\./, '_')
21 22
      end

23 24
      # def tables(name = nil) end

25 26 27 28
      def table_exists?(table_name)
        tables.include?(table_name.to_s)
      end

29 30 31
      # Returns an array of indexes for the given table.
      # def indexes(table_name, name = nil) end

32 33
      # Returns an array of Column objects for the table specified by +table_name+.
      # See the concrete implementation for details on the expected parameter values.
34 35
      def columns(table_name, name = nil) end

P
Pratik Naik 已提交
36 37 38
      # Creates a new table with the name +table_name+. +table_name+ may either
      # be a String or a Symbol.
      #
P
Pratik Naik 已提交
39
      # There are two ways to work with +create_table+.  You can use the block
40 41 42
      # form or the regular form, like this:
      #
      # === Block form
P
Pratik Naik 已提交
43 44 45
      #  # create_table() passes a TableDefinition object to the block.
      #  # This form will not only create the table, but also columns for the
      #  # table.
P
Pratik Naik 已提交
46
      #
47 48 49 50 51
      #  create_table(:suppliers) do |t|
      #    t.column :name, :string, :limit => 60
      #    # Other fields here
      #  end
      #
P
Pratik Naik 已提交
52 53 54 55 56 57 58
      # === Block form, with shorthand
      #  # You can also use the column types as method calls, rather than calling the column method.
      #  create_table(:suppliers) do |t|
      #    t.string :name, :limit => 60
      #    # Other fields here
      #  end
      #
59
      # === Regular form
P
Pratik Naik 已提交
60
      #  # Creates a table called 'suppliers' with no columns.
61
      #  create_table(:suppliers)
P
Pratik Naik 已提交
62
      #  # Add a column to 'suppliers'.
63 64 65 66
      #  add_column(:suppliers, :name, :string, {:limit => 60})
      #
      # The +options+ hash can include the following keys:
      # [<tt>:id</tt>]
67
      #   Whether to automatically add a primary key column. Defaults to true.
68
      #   Join tables for +has_and_belongs_to_many+ should set <tt>:id => false</tt>.
69 70 71 72 73 74 75
      # [<tt>:primary_key</tt>]
      #   The name of the primary key, if one is to be added automatically.
      #   Defaults to +id+.
      # [<tt>:options</tt>]
      #   Any extra options you want appended to the table definition.
      # [<tt>:temporary</tt>]
      #   Make a temporary table.
76
      # [<tt>:force</tt>]
77
      #   Set to true to drop the table before creating it.
78
      #   Defaults to false.
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
      #
      # ===== Examples
      # ====== Add a backend specific option to the generated SQL (MySQL)
      #  create_table(:suppliers, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
      # generates:
      #  CREATE TABLE suppliers (
      #    id int(11) DEFAULT NULL auto_increment PRIMARY KEY
      #  ) ENGINE=InnoDB DEFAULT CHARSET=utf8
      #
      # ====== Rename the primary key column
      #  create_table(:objects, :primary_key => 'guid') do |t|
      #    t.column :name, :string, :limit => 80
      #  end
      # generates:
      #  CREATE TABLE objects (
      #    guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
      #    name varchar(80)
      #  )
      #
      # ====== Do not add a primary key column
      #  create_table(:categories_suppliers, :id => false) do |t|
      #    t.column :category_id, :integer
      #    t.column :supplier_id, :integer
      #  end
      # generates:
104
      #  CREATE TABLE categories_suppliers (
105 106 107 108 109
      #    category_id int,
      #    supplier_id int
      #  )
      #
      # See also TableDefinition#column for details on how to create columns.
110
      def create_table(table_name, options = {})
111
        table_definition = TableDefinition.new(self)
112
        table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
113

114
        yield table_definition if block_given?
115

116 117
        if options[:force] && table_exists?(table_name)
          drop_table(table_name, options)
118 119
        end

120
        create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
121
        create_sql << "#{quote_table_name(table_name)} ("
122 123 124 125
        create_sql << table_definition.to_sql
        create_sql << ") #{options[:options]}"
        execute create_sql
      end
126

127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
      # A block for changing columns in +table+.
      #
      # === Example
      #  # change_table() yields a Table instance
      #  change_table(:suppliers) do |t|
      #    t.column :name, :string, :limit => 60
      #    # Other column alterations here
      #  end
      #
      # ===== Examples
      # ====== Add a column
      #  change_table(:suppliers) do |t|
      #    t.column :name, :string, :limit => 60
      #  end
      #
      # ====== Add 2 integer columns
      #  change_table(:suppliers) do |t|
      #    t.integer :width, :height, :null => false, :default => 0
      #  end
      #
      # ====== Add created_at/updated_at columns
      #  change_table(:suppliers) do |t|
      #    t.timestamps
      #  end
      #
      # ====== Add a foreign key column
      #  change_table(:suppliers) do |t|
      #    t.references :company
      #  end
      #
      # Creates a <tt>company_id(integer)</tt> column
      #
      # ====== Add a polymorphic foreign key column
      #  change_table(:suppliers) do |t|
      #    t.belongs_to :company, :polymorphic => true
      #  end
      #
      # Creates <tt>company_type(varchar)</tt> and <tt>company_id(integer)</tt> columns
      #
      # ====== Remove a column
      #  change_table(:suppliers) do |t|
      #    t.remove :company
      #  end
      #
171
      # ====== Remove several columns
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
      #  change_table(:suppliers) do |t|
      #    t.remove :company_id
      #    t.remove :width, :height
      #  end
      #
      # ====== Remove an index
      #  change_table(:suppliers) do |t|
      #    t.remove_index :company_id
      #  end
      #
      # See also Table for details on
      # all of the various column transformation
      def change_table(table_name)
        yield Table.new(table_name, self)
      end
187

188 189 190
      # Renames a table.
      # ===== Example
      #  rename_table('octopuses', 'octopi')
191
      def rename_table(table_name, new_name)
192 193
        raise NotImplementedError, "rename_table is not implemented"
      end
194

195
      # Drops a table from the database.
196 197
      def drop_table(table_name, options = {})
        execute "DROP TABLE #{quote_table_name(table_name)}"
198 199
      end

200 201
      # Adds a new column to the named table.
      # See TableDefinition#column for details of the options you can use.
202
      def add_column(table_name, column_name, type, options = {})
203
        add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
204 205 206 207
        add_column_options!(add_column_sql, options)
        execute(add_column_sql)
      end

208
      # Removes the column(s) from the table definition.
209 210
      # ===== Examples
      #  remove_column(:suppliers, :qualification)
211 212 213 214 215
      #  remove_columns(:suppliers, :qualification, :experience)
      def remove_column(table_name, *column_names)
        column_names.flatten.each do |column_name|
          execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
        end
216
      end
217
      alias :remove_columns :remove_column
218

219 220 221 222 223
      # Changes the column's definition according to the new options.
      # See TableDefinition#column for details of the options you can use.
      # ===== Examples
      #  change_column(:suppliers, :name, :string, :limit => 80)
      #  change_column(:accounts, :description, :text)
224 225 226 227
      def change_column(table_name, column_name, type, options = {})
        raise NotImplementedError, "change_column is not implemented"
      end

228 229
      # Sets a new default value for a column.  If you want to set the default
      # value to +NULL+, you are out of luck.  You need to
230
      # DatabaseStatements#execute the appropriate SQL statement yourself.
231 232 233
      # ===== Examples
      #  change_column_default(:suppliers, :qualification, 'new')
      #  change_column_default(:accounts, :authorized, 1)
234 235 236 237
      def change_column_default(table_name, column_name, default)
        raise NotImplementedError, "change_column_default is not implemented"
      end

238 239 240
      # Renames a column.
      # ===== Example
      #  rename_column(:suppliers, :description, :name)
241 242 243 244
      def rename_column(table_name, column_name, new_column_name)
        raise NotImplementedError, "rename_column is not implemented"
      end

245 246 247
      # Adds a new index to the table.  +column_name+ can be a single Symbol, or
      # an Array of Symbols.
      #
248
      # The index will be named after the table and the first column name,
P
Pratik Naik 已提交
249
      # unless you pass <tt>:name</tt> as an option.
250
      #
251 252
      # When creating an index on multiple columns, the first column is used as a name
      # for the index. For example, when you specify an index on two columns
P
Pratik Naik 已提交
253 254
      # [<tt>:first</tt>, <tt>:last</tt>], the DBMS creates an index for both columns as well as an
      # index for the first column <tt>:first</tt>. Using just the first name for this index
255 256 257
      # makes sense, because you will never have to create a singular index with this
      # name.
      #
258
      # ===== Examples
259
      #
260 261 262 263
      # ====== Creating a simple index
      #  add_index(:suppliers, :name)
      # generates
      #  CREATE INDEX suppliers_name_index ON suppliers(name)
264
      #
265 266 267
      # ====== Creating a unique index
      #  add_index(:accounts, [:branch_id, :party_id], :unique => true)
      # generates
268
      #  CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
269
      #
270 271 272 273
      # ====== Creating a named index
      #  add_index(:accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party')
      # generates
      #  CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
274 275 276 277 278 279 280 281 282 283 284
      #
      # ====== Creating an index with specific key length
      #  add_index(:accounts, :name, :name => 'by_name', :length => 10)
      # generates
      #  CREATE INDEX by_name ON accounts(name(10))
      #
      #  add_index(:accounts, [:name, :surname], :name => 'by_name_surname', :length => {:name => 10, :surname => 15})
      # generates
      #  CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
      #
      # Note: SQLite doesn't support index length
285
      def add_index(table_name, column_name, options = {})
286
        column_names = Array.wrap(column_name)
287
        index_name   = index_name(table_name, :column => column_names)
288 289 290 291 292 293 294

        if Hash === options # legacy support, since this param was a string
          index_type = options[:unique] ? "UNIQUE" : ""
          index_name = options[:name] || index_name
        else
          index_type = options
        end
295 296 297

        quoted_column_names = quoted_columns_for_index(column_names, options).join(", ")

298
        execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
299 300 301 302
      end

      # Remove the given index from the table.
      #
303
      # Remove the suppliers_name_index in the suppliers table.
304
      #   remove_index :suppliers, :name
305
      # Remove the index named accounts_branch_id_index in the accounts table.
306
      #   remove_index :accounts, :column => :branch_id
307 308
      # Remove the index named accounts_branch_id_party_id_index in the accounts table.
      #   remove_index :accounts, :column => [:branch_id, :party_id]
309 310
      # Remove the index named by_branch_party in the accounts table.
      #   remove_index :accounts, :name => :by_branch_party
311
      def remove_index(table_name, options = {})
312
        execute "DROP INDEX #{quote_column_name(index_name(table_name, options))} ON #{quote_table_name(table_name)}"
313 314 315
      end

      def index_name(table_name, options) #:nodoc:
316 317
        if Hash === options # legacy support
          if options[:column]
318
            "index_#{table_name}_on_#{Array.wrap(options[:column]) * '_and_'}"
319
          elsif options[:name]
320
            options[:name]
321 322 323 324
          else
            raise ArgumentError, "You must specify the index name"
          end
        else
325
          index_name(table_name, :column => options)
326 327 328
        end
      end

329 330 331
      # Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the
      # entire structure of the database.
      def structure_dump
332 333
      end

334 335 336
      def dump_schema_information #:nodoc:
        sm_table = ActiveRecord::Migrator.schema_migrations_table_name
        migrated = select_values("SELECT version FROM #{sm_table}")
337
        migrated.map { |v| "INSERT INTO #{sm_table} (version) VALUES ('#{v}');" }.join("\n\n")
338 339
      end

340 341
      # Should not be called normally, but this operation is non-destructive.
      # The migrations module handles this automatically.
342 343 344
      def initialize_schema_migrations_table
        sm_table = ActiveRecord::Migrator.schema_migrations_table_name

345
        unless table_exists?(sm_table)
346 347
          create_table(sm_table, :id => false) do |schema_migrations_table|
            schema_migrations_table.column :version, :string, :null => false
348
          end
349
          add_index sm_table, :version, :unique => true,
350
            :name => "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
351 352 353 354 355

          # Backwards-compatibility: if we find schema_info, assume we've
          # migrated up to that point:
          si_table = Base.table_name_prefix + 'schema_info' + Base.table_name_suffix

356
          if table_exists?(si_table)
357

358 359 360
            old_version = select_value("SELECT version FROM #{quote_table_name(si_table)}").to_i
            assume_migrated_upto_version(old_version)
            drop_table(si_table)
361 362 363 364
          end
        end
      end

365
      def assume_migrated_upto_version(version, migrations_path = ActiveRecord::Migrator.migrations_path)
366
        version = version.to_i
367
        sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
368

369
        migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i)
370
        versions = Dir["#{migrations_path}/[0-9]*_*.rb"].map do |filename|
371 372 373
          filename.split('/').last.split('_').first.to_i
        end

374 375 376 377 378 379 380 381 382 383 384 385
        unless migrated.include?(version)
          execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')"
        end

        inserted = Set.new
        (versions - migrated).each do |v|
          if inserted.include?(v)
            raise "Duplicate migration #{v}. Please renumber your migrations to resolve the conflict."
          elsif v < version
            execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
            inserted << v
          end
386 387
        end
      end
388

389
      def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
390
        if native = native_database_types[type]
391
          column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
392

393
          if type == :decimal # ignore limit, use precision and scale
394
            scale ||= native[:scale]
395 396

            if precision ||= native[:precision]
397 398 399 400 401
              if scale
                column_type_sql << "(#{precision},#{scale})"
              else
                column_type_sql << "(#{precision})"
              end
402 403
            elsif scale
              raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale if specified"
404
            end
405

406
          elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
407
            column_type_sql << "(#{limit})"
408
          end
409 410

          column_type_sql
411
        else
412
          type
413
        end
414 415
      end

416
      def add_column_options!(sql, options) #:nodoc:
417
        sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
P
Pratik Naik 已提交
418
        # must explicitly check for :null to allow change_column to work on migrations
419 420
        if options[:null] == false
          sql << " NOT NULL"
421
        end
422
      end
423

424 425 426 427 428
      # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
      # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax.
      #
      #   distinct("posts.id", "posts.created_at desc")
      def distinct(columns, order_by)
429 430
        "DISTINCT #{columns}"
      end
431

432 433 434 435 436
      # Adds timestamps (created_at and updated_at) columns to the named table.
      # ===== Examples
      #  add_timestamps(:suppliers)
      def add_timestamps(table_name)
        add_column table_name, :created_at, :datetime
437
        add_column table_name, :updated_at, :datetime
438
      end
439

440 441 442 443
      # Removes the timestamp columns (created_at and updated_at) from the table definition.
      # ===== Examples
      #  remove_timestamps(:suppliers)
      def remove_timestamps(table_name)
444 445
        remove_column table_name, :updated_at
        remove_column table_name, :created_at
446
      end
447

448
      protected
449 450 451 452 453
        # Overridden by the mysql adapter for supporting index lengths
        def quoted_columns_for_index(column_names, options = {})
          column_names.map {|name| quote_column_name(name) }
        end

454 455 456
        def options_include_default?(options)
          options.include?(:default) && !(options[:null] == false && options[:default].nil?)
        end
457 458
    end
  end
459
end