abstract_mysql_adapter.rb 27.1 KB
Newer Older
1
require 'arel/visitors/bind_visitor'
2 3 4 5

module ActiveRecord
  module ConnectionAdapters
    class AbstractMysqlAdapter < AbstractAdapter
6 7
      include Savepoints

8
      class SchemaCreation < AbstractAdapter::SchemaCreation
R
Rafael Mendonça França 已提交
9 10 11 12 13 14
        def visit_AddColumn(o)
          add_column_position!(super, column_options(o))
        end

        private

15
        def visit_TableDefinition(o)
R
Rafael Mendonça França 已提交
16 17 18 19 20 21
          name = o.name
          create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} "

          statements = o.columns.map { |c| accept c }
          statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) })

22
          create_sql << "(#{statements.join(', ')}) " if statements.present?
23 24 25 26
          create_sql << "#{o.options}"
          create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
          create_sql
        end
27

28 29 30 31 32 33 34
        def visit_ChangeColumnDefinition(o)
          column = o.column
          options = o.options
          sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale])
          change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}"
          add_column_options!(change_column_sql, options)
          add_column_position!(change_column_sql, options)
35 36
        end

37 38
        def add_column_position!(sql, options)
          if options[:first]
39
            sql << " FIRST"
40 41
          elsif options[:after]
            sql << " AFTER #{quote_column_name(options[:after])}"
42 43 44
          end
          sql
        end
45 46

        def index_in_create(table_name, column_name, options)
47
          index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
48
          "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
49
        end
50 51 52 53 54 55
      end

      def schema_creation
        SchemaCreation.new self
      end

56
      class Column < ConnectionAdapters::Column # :nodoc:
57
        attr_reader :collation, :strict, :extra
58

59
        def initialize(name, default, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
60
          @strict    = strict
61
          @collation = collation
62
          @extra     = extra
63
          super(name, default, sql_type, null)
64 65
        end

66
        def extract_default(default)
67
          if blob_or_text_column?
68
            if default.blank?
69
              null || strict ? nil : ''
70 71 72 73 74 75 76 77 78 79 80
            else
              raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
            end
          elsif missing_default_forged_as_empty_string?(default)
            nil
          else
            super
          end
        end

        def has_default?
81
          return false if blob_or_text_column? #mysql forbids defaults on blob and text columns
82 83
          super
        end
84

85 86 87
        def blob_or_text_column?
          sql_type =~ /blob/i || type == :text
        end
88

89 90 91 92 93
        # Must return the relevant concrete adapter
        def adapter
          raise NotImplementedError
        end

94 95 96 97
        def case_sensitive?
          collation && !collation.match(/_ci$/)
        end

98 99
        private

100 101 102 103 104 105 106 107 108 109 110 111
        def simplified_type(field_type)
          return :boolean if adapter.emulate_booleans && field_type.downcase.index("tinyint(1)")

          case field_type
          when /enum/i, /set/i then :string
          when /year/i         then :integer
          when /bit/i          then :binary
          else
            super
          end
        end

112 113
        def extract_limit(sql_type)
          case sql_type
M
masarakki 已提交
114 115
          when /^enum\((.+)\)/i
            $1.split(',').map{|enum| enum.strip.length - 2}.max
116 117 118 119 120 121 122 123 124 125 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
          when /blob|text/i
            case sql_type
            when /tiny/i
              255
            when /medium/i
              16777215
            when /long/i
              2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
            else
              super # we could return 65535 here, but we leave it undecorated by default
            end
          when /^bigint/i;    8
          when /^int/i;       4
          when /^mediumint/i; 3
          when /^smallint/i;  2
          when /^tinyint/i;   1
          else
            super
          end
        end

        # MySQL misreports NOT NULL column default when none is given.
        # We can't detect this for columns which may have a legitimate ''
        # default (string) but we can for others (integer, datetime, boolean,
        # and the rest).
        #
        # Test whether the column has default '', is not null, and is not
        # a type allowing default ''.
        def missing_default_forged_as_empty_string?(default)
          type != :string && !null && default == ''
        end
      end

      ##
      # :singleton-method:
      # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
      # as boolean. If you wish to disable this emulation (which was the default
      # behavior in versions 0.13.1 and earlier) you can add the following line
      # to your application.rb file:
      #
      #   ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
      class_attribute :emulate_booleans
      self.emulate_booleans = true

      LOST_CONNECTION_ERROR_MESSAGES = [
        "Server shutdown in progress",
        "Broken pipe",
        "Lost connection to MySQL server during query",
        "MySQL server has gone away" ]

      QUOTED_TRUE, QUOTED_FALSE = '1', '0'

      NATIVE_DATABASE_TYPES = {
169
        :primary_key => "int(11) auto_increment PRIMARY KEY",
170 171 172 173 174 175 176 177 178 179 180 181 182
        :string      => { :name => "varchar", :limit => 255 },
        :text        => { :name => "text" },
        :integer     => { :name => "int", :limit => 4 },
        :float       => { :name => "float" },
        :decimal     => { :name => "decimal" },
        :datetime    => { :name => "datetime" },
        :timestamp   => { :name => "datetime" },
        :time        => { :name => "time" },
        :date        => { :name => "date" },
        :binary      => { :name => "blob" },
        :boolean     => { :name => "tinyint", :limit => 1 }
      }

183 184 185
      INDEX_TYPES  = [:fulltext, :spatial]
      INDEX_USINGS = [:btree, :hash]

186 187 188 189
      class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
        include Arel::Visitors::BindVisitor
      end

190 191 192 193 194
      # FIXME: Make the first parameter more similar for the two adapters
      def initialize(connection, logger, connection_options, config)
        super(connection, logger)
        @connection_options, @config = connection_options, config
        @quoted_column_names, @quoted_table_names = {}, {}
195

196
        if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
197
          @prepared_statements = true
198 199
          @visitor = Arel::Visitors::MySQL.new self
        else
200
          @visitor = unprepared_visitor
201
        end
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
      end

      def adapter_name #:nodoc:
        self.class::ADAPTER_NAME
      end

      # Returns true, since this connection adapter supports migrations.
      def supports_migrations?
        true
      end

      def supports_primary_key?
        true
      end

217 218 219 220
      def supports_bulk_alter? #:nodoc:
        true
      end

221
      # Technically MySQL allows to create indexes with the sort order syntax
222 223 224 225 226
      # but at the moment (5.5) it doesn't yet implement them
      def supports_index_sort_order?
        true
      end

227
      def type_cast(value, column)
228 229 230 231 232 233 234 235
        case value
        when TrueClass
          1
        when FalseClass
          0
        else
          super
        end
236 237
      end

238 239 240 241 242 243 244 245
      # MySQL 4 technically support transaction isolation, but it is affected by a bug
      # where the transaction level gets persisted for the whole session:
      #
      # http://bugs.mysql.com/bug.php?id=39170
      def supports_transaction_isolation?
        version[0] >= 5
      end

246 247 248 249
      def supports_indexes_in_create?
        true
      end

250 251 252 253
      def native_database_types
        NATIVE_DATABASE_TYPES
      end

254 255 256 257
      def index_algorithms
        { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
      end

258 259 260 261 262 263 264 265 266
      # HELPER METHODS ===========================================

      # The two drivers have slightly different ways of yielding hashes of results, so
      # this method must be implemented to provide a uniform interface.
      def each_hash(result) # :nodoc:
        raise NotImplementedError
      end

      # Overridden by the adapters to instantiate their specific Column type.
267 268
      def new_column(field, default, type, null, collation, extra = "") # :nodoc:
        Column.new(field, default, type, null, collation, extra)
269 270 271 272 273 274 275 276 277 278 279
      end

      # Must return the Mysql error number from the exception, if the exception has an
      # error number.
      def error_number(exception) # :nodoc:
        raise NotImplementedError
      end

      # QUOTING ==================================================

      def quote(value, column = nil)
280 281
        if value.kind_of?(String) && column && column.type == :binary
          s = value.unpack("H*")[0]
282
          "x'#{s}'"
283 284
        elsif value.kind_of?(BigDecimal)
          value.to_s("F")
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
        else
          super
        end
      end

      def quote_column_name(name) #:nodoc:
        @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
      end

      def quote_table_name(name) #:nodoc:
        @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
      end

      def quoted_true
        QUOTED_TRUE
      end

      def quoted_false
        QUOTED_FALSE
      end

      # REFERENTIAL INTEGRITY ====================================

R
Rafael Mendonça França 已提交
308
      def disable_referential_integrity #:nodoc:
309 310 311 312 313 314 315 316 317 318 319 320 321 322
        old = select_value("SELECT @@FOREIGN_KEY_CHECKS")

        begin
          update("SET FOREIGN_KEY_CHECKS = 0")
          yield
        ensure
          update("SET FOREIGN_KEY_CHECKS = #{old}")
        end
      end

      # DATABASE STATEMENTS ======================================

      # Executes the SQL statement in the context of this connection.
      def execute(sql, name = nil)
323
        log(sql, name) { @connection.query(sql) }
324 325 326
      end

      # MysqlAdapter has to free a result after using it, so we use this method to write
327
      # stuff in an abstract way without concerning ourselves about whether it needs to be
328 329 330 331 332 333 334 335 336 337 338 339 340 341
      # explicitly freed or not.
      def execute_and_free(sql, name = nil) #:nodoc:
        yield execute(sql, name)
      end

      def update_sql(sql, name = nil) #:nodoc:
        super
        @connection.affected_rows
      end

      def begin_db_transaction
        execute "BEGIN"
      end

342 343 344 345 346
      def begin_isolated_db_transaction(isolation)
        execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
        begin_db_transaction
      end

347 348 349 350 351 352 353 354 355 356
      def commit_db_transaction #:nodoc:
        execute "COMMIT"
      end

      def rollback_db_transaction #:nodoc:
        execute "ROLLBACK"
      end

      # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
      # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
357
      # these, we must use a subquery.
358 359
      def join_to_update(update, select) #:nodoc:
        if select.limit || select.offset || select.orders.any?
360
          super
361 362 363 364 365 366
        else
          update.table select.source
          update.wheres = select.constraints
        end
      end

J
Jon Leighton 已提交
367 368 369 370
      def empty_insert_statement_value
        "VALUES ()"
      end

371 372 373 374 375 376
      # SCHEMA STATEMENTS ========================================

      # Drops the database specified on the +name+ attribute
      # and creates it again using the provided +options+.
      def recreate_database(name, options = {})
        drop_database(name)
377
        sql = create_database(name, options)
378
        reconnect!
379
        sql
380 381 382 383 384 385
      end

      # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
      # Charset defaults to utf8.
      #
      # Example:
A
AvnerCohen 已提交
386
      #   create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
387
      #   create_database 'matt_development'
A
AvnerCohen 已提交
388
      #   create_database 'matt_development', charset: :big5
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
      def create_database(name, options = {})
        if options[:collation]
          execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
        else
          execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
        end
      end

      # Drops a MySQL database.
      #
      # Example:
      #   drop_database('sebastian_development')
      def drop_database(name) #:nodoc:
        execute "DROP DATABASE IF EXISTS `#{name}`"
      end

      def current_database
        select_value 'SELECT DATABASE() as db'
      end

      # Returns the database character set.
      def charset
        show_variable 'character_set_database'
      end

      # Returns the database collation strategy.
      def collation
        show_variable 'collation_database'
      end

419 420
      def tables(name = nil, database = nil, like = nil) #:nodoc:
        sql = "SHOW TABLES "
421
        sql << "IN #{quote_table_name(database)} " if database
422
        sql << "LIKE #{quote(like)}" if like
423 424 425 426 427 428 429

        execute_and_free(sql, 'SCHEMA') do |result|
          result.collect { |field| field.first }
        end
      end

      def table_exists?(name)
430 431
        return false unless name
        return true if tables(nil, nil, name).any?
432 433 434 435 436 437 438 439 440

        name          = name.to_s
        schema, table = name.split('.', 2)

        unless table # A table was provided without a schema
          table  = schema
          schema = nil
        end

441
        tables(nil, schema, table).any?
442 443 444 445 446 447 448 449 450 451 452
      end

      # Returns an array of indexes for the given table.
      def indexes(table_name, name = nil) #:nodoc:
        indexes = []
        current_index = nil
        execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
          each_hash(result) do |row|
            if current_index != row[:Key_name]
              next if row[:Key_name] == 'PRIMARY' # skip the primary key
              current_index = row[:Key_name]
453 454 455 456 457

              mysql_index_type = row[:Index_type].downcase.to_sym
              index_type  = INDEX_TYPES.include?(mysql_index_type)  ? mysql_index_type : nil
              index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
              indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
458 459 460 461 462 463 464 465 466 467 468
            end

            indexes.last.columns << row[:Column_name]
            indexes.last.lengths << row[:Sub_part]
          end
        end

        indexes
      end

      # Returns an array of +Column+ objects for the table specified by +table_name+.
469
      def columns(table_name)#:nodoc:
470
        sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
471 472
        execute_and_free(sql, 'SCHEMA') do |result|
          each_hash(result).map do |field|
473 474
            field_name = set_field_encoding(field[:Field])
            new_column(field_name, field[:Default], field[:Type], field[:Null] == "YES", field[:Collation], field[:Extra])
475 476 477 478 479 480 481 482
          end
        end
      end

      def create_table(table_name, options = {}) #:nodoc:
        super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
      end

483
      def bulk_change_table(table_name, operations) #:nodoc:
484
        sqls = operations.flat_map do |command, args|
485 486 487
          table, arguments = args.shift, args
          method = :"#{command}_sql"

488
          if respond_to?(method, true)
489 490 491 492
            send(method, table, *arguments)
          else
            raise "Unknown method called : #{method}(#{arguments.inspect})"
          end
493
        end.join(", ")
494 495 496 497

        execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
      end

498 499 500 501 502 503
      # Renames a table.
      #
      # Example:
      #   rename_table('octopuses', 'octopi')
      def rename_table(table_name, new_name)
        execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
504
        rename_table_indexes(table_name, new_name)
505 506
      end

507 508 509 510
      def drop_table(table_name, options = {})
        execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}"
      end

511
      def rename_index(table_name, old_name, new_name)
512
        if (version[0] == 5 && version[1] >= 7) || version[0] >= 6
513 514 515 516 517 518
          execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
        else
          super
        end
      end

519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539
      def change_column_default(table_name, column_name, default)
        column = column_for(table_name, column_name)
        change_column table_name, column_name, column.sql_type, :default => default
      end

      def change_column_null(table_name, column_name, null, default = nil)
        column = column_for(table_name, column_name)

        unless null || default.nil?
          execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
        end

        change_column table_name, column_name, column.sql_type, :null => null
      end

      def change_column(table_name, column_name, type, options = {}) #:nodoc:
        execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
      end

      def rename_column(table_name, column_name, new_column_name) #:nodoc:
        execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
540
        rename_column_indexes(table_name, column_name, new_column_name)
541 542
      end

D
doabit 已提交
543
      def add_index(table_name, column_name, options = {}) #:nodoc:
544 545
        index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
        execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
D
doabit 已提交
546 547
      end

548 549
      # Maps logical Rails types to MySQL-specific data types.
      def type_to_sql(type, limit = nil, precision = nil, scale = nil)
550
        case type.to_s
551 552 553 554 555 556 557
        when 'binary'
          case limit
          when 0..0xfff;           "varbinary(#{limit})"
          when nil;                "blob"
          when 0x1000..0xffffffff; "blob(#{limit})"
          else raise(ActiveRecordError, "No binary type has character length #{limit}")
          end
558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576
        when 'integer'
          case limit
          when 1; 'tinyint'
          when 2; 'smallint'
          when 3; 'mediumint'
          when nil, 4, 11; 'int(11)'  # compatibility with MySQL default
          when 5..8; 'bigint'
          else raise(ActiveRecordError, "No integer type has byte size #{limit}")
          end
        when 'text'
          case limit
          when 0..0xff;               'tinytext'
          when nil, 0x100..0xffff;    'text'
          when 0x10000..0xffffff;     'mediumtext'
          when 0x1000000..0xffffffff; 'longtext'
          else raise(ActiveRecordError, "No text type has character length #{limit}")
          end
        else
          super
577 578 579 580 581 582 583 584 585 586 587 588 589
        end
      end

      def add_column_position!(sql, options)
        if options[:first]
          sql << " FIRST"
        elsif options[:after]
          sql << " AFTER #{quote_column_name(options[:after])}"
        end
      end

      # SHOW VARIABLES LIKE 'name'
      def show_variable(name)
K
kennyj 已提交
590
        variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA')
591 592 593 594 595
        variables.first['Value'] unless variables.empty?
      end

      # Returns a table's primary key and belonging sequence.
      def pk_and_sequence_for(table)
K
kennyj 已提交
596 597
        execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
          create_table = each_hash(result).first[:"Create Table"]
598
          if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
S
Seamus Abshere 已提交
599
            keys = $1.split(",").map { |key| key.delete('`"') }
K
kennyj 已提交
600 601 602 603
            keys.length == 1 ? [keys.first, nil] : nil
          else
            nil
          end
604 605 606 607 608 609 610 611 612 613 614 615 616
        end
      end

      # Returns just a table's primary key
      def primary_key(table)
        pk_and_sequence = pk_and_sequence_for(table)
        pk_and_sequence && pk_and_sequence.first
      end

      def case_sensitive_modifier(node)
        Arel::Nodes::Bin.new(node)
      end

617 618 619 620 621 622 623 624
      def case_sensitive_comparison(table, attribute, column, value)
        if column.case_sensitive?
          table[attribute].eq(value)
        else
          super
        end
      end

625 626 627 628 629 630 631 632
      def case_insensitive_comparison(table, attribute, column, value)
        if column.case_sensitive?
          super
        else
          table[attribute].eq(value)
        end
      end

633 634 635 636
      def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
        where_sql
      end

637
      def strict_mode?
638
        self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
639 640
      end

641 642 643 644
      def valid_type?(type)
        !native_database_types[type].nil?
      end

645 646
      protected

647 648 649 650 651 652 653 654 655 656 657
      # MySQL is too stupid to create a temporary table for use subquery, so we have
      # to give it some prompting in the form of a subsubquery. Ugh!
      def subquery_for(key, select)
        subsubselect = select.clone
        subsubselect.projections = [key]

        subselect = Arel::SelectManager.new(select.engine)
        subselect.project Arel.sql(key.name)
        subselect.from subsubselect.as('__active_record_temp')
      end

658 659 660 661
      def add_index_length(option_strings, column_names, options = {})
        if options.is_a?(Hash) && length = options[:length]
          case length
          when Hash
662
            column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
663 664 665 666 667 668 669 670
          when Fixnum
            column_names.each {|name| option_strings[name] += "(#{length})"}
          end
        end

        return option_strings
      end

671
      def quoted_columns_for_index(column_names, options = {})
672
        option_strings = Hash[column_names.map {|name| [name, '']}]
673

674 675 676 677 678 679 680
        # add index length
        option_strings = add_index_length(option_strings, column_names, options)

        # add index sort order
        option_strings = add_index_sort_order(option_strings, column_names, options)

        column_names.map {|name| quote_column_name(name) + option_strings[name]}
681 682 683 684 685 686 687 688 689 690 691 692 693 694
      end

      def translate_exception(exception, message)
        case error_number(exception)
        when 1062
          RecordNotUnique.new(message, exception)
        when 1452
          InvalidForeignKey.new(message, exception)
        else
          super
        end
      end

      def add_column_sql(table_name, column_name, type, options = {})
695 696 697
        td = create_table_definition table_name, options[:temporary], options[:options]
        cd = td.new_column_definition(column_name, type, options)
        schema_creation.visit_AddColumn cd
698 699 700 701 702 703 704 705 706 707 708 709 710
      end

      def change_column_sql(table_name, column_name, type, options = {})
        column = column_for(table_name, column_name)

        unless options_include_default?(options)
          options[:default] = column.default
        end

        unless options.has_key?(:null)
          options[:null] = column.null
        end

711 712
        options[:name] = column.name
        schema_creation.accept ChangeColumnDefinition.new column, type, options
713 714 715
      end

      def rename_column_sql(table_name, column_name, new_column_name)
716
        options = { name: new_column_name }
717 718 719 720

        if column = columns(table_name).find { |c| c.name == column_name.to_s }
          options[:default] = column.default
          options[:null] = column.null
721
          options[:auto_increment] = (column.extra == "auto_increment")
722 723 724 725
        else
          raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
        end

K
kennyj 已提交
726
        current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
727
        schema_creation.accept ChangeColumnDefinition.new column, current_type, options
728 729
      end

M
Marc-Andre Lafortune 已提交
730 731 732 733 734 735
      def remove_column_sql(table_name, column_name, type = nil, options = {})
        "DROP #{quote_column_name(column_name)}"
      end

      def remove_columns_sql(table_name, *column_names)
        column_names.map {|column_name| remove_column_sql(table_name, column_name) }
736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755
      end

      def add_index_sql(table_name, column_name, options = {})
        index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
        "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
      end

      def remove_index_sql(table_name, options = {})
        index_name = index_name_for_remove(table_name, options)
        "DROP INDEX #{index_name}"
      end

      def add_timestamps_sql(table_name)
        [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
      end

      def remove_timestamps_sql(table_name)
        [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
      end

756 757 758 759 760 761 762 763 764 765 766 767
      private

      def supports_views?
        version[0] >= 5
      end

      def column_for(table_name, column_name)
        unless column = columns(table_name).find { |c| c.name == column_name.to_s }
          raise "No such column: #{table_name}.#{column_name}"
        end
        column
      end
768 769 770 771 772 773 774 775 776 777 778

      def configure_connection
        variables = @config[:variables] || {}

        # By default, MySQL 'where id is null' selects the last inserted id.
        # Turn this off. http://dev.rubyonrails.org/ticket/6778
        variables[:sql_auto_is_null] = 0

        # Increase timeout so the server doesn't disconnect us.
        wait_timeout = @config[:wait_timeout]
        wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
779
        variables[:wait_timeout] = self.class.type_cast_config_to_integer(wait_timeout)
780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803

        # Make MySQL reject illegal values rather than truncating or blanking them, see
        # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
        # If the user has provided another value for sql_mode, don't replace it.
        if strict_mode? && !variables.has_key?(:sql_mode)
          variables[:sql_mode] = 'STRICT_ALL_TABLES'
        end

        # NAMES does not have an equals sign, see
        # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
        # (trailing comma because variable_assignments will always have content)
        encoding = "NAMES #{@config[:encoding]}, " if @config[:encoding]

        # Gather up all of the SET variables...
        variable_assignments = variables.map do |k, v|
          if v == ':default' || v == :default
            "@@SESSION.#{k.to_s} = DEFAULT" # Sets the value to the global or compile default
          elsif !v.nil?
            "@@SESSION.#{k.to_s} = #{quote(v)}"
          end
          # or else nil; compact to clear nils out
        end.compact.join(', ')

        # ...and send them all in one query
804
        @connection.query  "SET #{encoding} #{variable_assignments}"
805
      end
806 807 808
    end
  end
end