mysql_adapter.rb 29.2 KB
Newer Older
D
Initial  
David Heinemeier Hansson 已提交
1
require 'active_record/connection_adapters/abstract_adapter'
J
Jeremy Kemper 已提交
2
require 'active_support/core_ext/kernel/requires'
3
require 'active_support/core_ext/object/blank'
4
require 'set'
5

A
Aaron Patterson 已提交
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
begin
  require 'mysql'
rescue LoadError
  raise "!!! Missing the mysql gem. Add it to your Gemfile: gem 'mysql'"
end

unless defined?(Mysql::Result) && Mysql::Result.method_defined?(:each_hash)
  raise "!!! Outdated mysql gem. Upgrade to 2.8.1 or later. In your Gemfile: gem 'mysql', '2.8.1'. Or use gem 'mysql2'"
end

class Mysql
  class Time
    ###
    # This monkey patch is for test_additional_columns_from_join_table
    def to_date
      Date.new(year, month, day)
    end
  end
24 25
  class Stmt; include Enumerable end
  class Result; include Enumerable end
26 27
end

D
Initial  
David Heinemeier Hansson 已提交
28 29
module ActiveRecord
  class Base
30 31
    # Establishes a connection to the database that's used by all Active Record objects.
    def self.mysql_connection(config) # :nodoc:
32
      config = config.symbolize_keys
D
Initial  
David Heinemeier Hansson 已提交
33 34 35 36 37
      host     = config[:host]
      port     = config[:port]
      socket   = config[:socket]
      username = config[:username] ? config[:username].to_s : 'root'
      password = config[:password].to_s
38
      database = config[:database]
39

40
      mysql = Mysql.init
41
      mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslca] || config[:sslkey]
42

43
      default_flags = Mysql.const_defined?(:CLIENT_MULTI_RESULTS) ? Mysql::CLIENT_MULTI_RESULTS : 0
44
      default_flags |= Mysql::CLIENT_FOUND_ROWS if Mysql.const_defined?(:CLIENT_FOUND_ROWS)
45 46
      options = [host, username, password, database, port, socket, default_flags]
      ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config)
D
Initial  
David Heinemeier Hansson 已提交
47 48
    end
  end
49

D
Initial  
David Heinemeier Hansson 已提交
50
  module ConnectionAdapters
51
    class MysqlColumn < Column #:nodoc:
A
Aaron Patterson 已提交
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
      class << self
        def string_to_time(value)
          return super unless Mysql::Time === value
          new_time(
            value.year,
            value.month,
            value.day,
            value.hour,
            value.minute,
            value.second,
            value.second_part)
        end

        def string_to_dummy_time(v)
          return super unless Mysql::Time === v
          new_time(2000, 01, 01, v.hour, v.minute, v.second, v.second_part)
        end

        def string_to_date(v)
          return super unless Mysql::Time === v
          new_date(v.year, v.month, v.day)
        end
      end

76
      def extract_default(default)
77
        if sql_type =~ /blob/i || type == :text
78
          if default.blank?
79
            return null ? nil : ''
80 81 82 83 84 85 86 87
          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
88 89
      end

90
      def has_default?
91
        return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
92 93 94
        super
      end

95 96
      private
        def simplified_type(field_type)
97
          return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
98
          return :string  if field_type =~ /enum/i
99 100
          super
        end
101

102
        def extract_limit(sql_type)
103 104
          case sql_type
          when /blob|text/i
105 106 107 108 109 110 111 112 113 114
            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
115
          when /^bigint/i;    8
J
Jeremy Kemper 已提交
116
          when /^int/i;       4
117
          when /^mediumint/i; 3
J
Jeremy Kemper 已提交
118 119
          when /^smallint/i;  2
          when /^tinyint/i;   1
120 121 122 123 124
          else
            super
          end
        end

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

137 138 139 140 141
    # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
    # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
    #
    # Options:
    #
P
Pratik Naik 已提交
142 143 144 145 146 147 148
    # * <tt>:host</tt> - Defaults to "localhost".
    # * <tt>:port</tt> - Defaults to 3306.
    # * <tt>:socket</tt> - Defaults to "/tmp/mysql.sock".
    # * <tt>:username</tt> - Defaults to "root"
    # * <tt>:password</tt> - Defaults to nothing.
    # * <tt>:database</tt> - The name of the database. No default, must be provided.
    # * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
149
    # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
150
    # * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
P
Pratik Naik 已提交
151 152 153 154
    # * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
    # * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
    # * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
    # * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
155
    #
156
    class MysqlAdapter < AbstractAdapter
P
Pratik Naik 已提交
157 158 159 160 161 162

      ##
      # :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
163
      # to your application.rb file:
P
Pratik Naik 已提交
164 165
      #
      #   ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
166
      cattr_accessor :emulate_booleans
167
      self.emulate_booleans = true
168

A
Aaron Patterson 已提交
169
      ADAPTER_NAME = 'MySQL'
170

171
      LOST_CONNECTION_ERROR_MESSAGES = [
172
        "Server shutdown in progress",
173 174
        "Broken pipe",
        "Lost connection to MySQL server during query",
175 176
        "MySQL server has gone away" ]

A
Aaron Patterson 已提交
177
      QUOTED_TRUE, QUOTED_FALSE = '1', '0'
178

179
      NATIVE_DATABASE_TYPES = {
A
Aaron Patterson 已提交
180
        :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
181 182
        :string      => { :name => "varchar", :limit => 255 },
        :text        => { :name => "text" },
183
        :integer     => { :name => "int", :limit => 4 },
184 185 186 187 188 189 190 191 192 193
        :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 }
      }

J
Jeremy Kemper 已提交
194
      def initialize(connection, logger, connection_options, config)
195
        super(connection, logger)
J
Jeremy Kemper 已提交
196
        @connection_options, @config = connection_options, config
197
        @quoted_column_names, @quoted_table_names = {}, {}
198
        @statements = {}
199
        @client_encoding = nil
200
        connect
201 202 203
      end

      def adapter_name #:nodoc:
204
        ADAPTER_NAME
205 206
      end

207 208 209 210
      def supports_bulk_alter? #:nodoc:
        true
      end

211 212 213 214 215 216
      # Returns +true+ when the connection adapter supports prepared statement
      # caching, otherwise returns +false+
      def supports_statement_cache?
        true
      end

217
      def supports_migrations? #:nodoc:
218 219
        true
      end
E
Emilio Tagua 已提交
220

221 222 223 224
      def supports_primary_key? #:nodoc:
        true
      end

225 226 227
      def supports_savepoints? #:nodoc:
        true
      end
228

229
      def native_database_types #:nodoc:
230
        NATIVE_DATABASE_TYPES
231 232
      end

233

234 235
      # QUOTING ==================================================

236
      def quote(value, column = nil)
237
        if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
238 239
          s = column.class.string_to_binary(value).unpack("H*")[0]
          "x'#{s}'"
240
        elsif value.kind_of?(BigDecimal)
241
          value.to_s("F")
242 243 244 245 246
        else
          super
        end
      end

247 248 249 250 251 252
      def type_cast(value, column)
        return super unless value == true || value == false

        value ? 1 : 0
      end

253
      def quote_column_name(name) #:nodoc:
254
        @quoted_column_names[name] ||= "`#{name}`"
255 256
      end

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

261
      def quote_string(string) #:nodoc:
262
        @connection.quote(string)
D
Initial  
David Heinemeier Hansson 已提交
263
      end
264

265
      def quoted_true
266
        QUOTED_TRUE
267
      end
268

269
      def quoted_false
270
        QUOTED_FALSE
D
Initial  
David Heinemeier Hansson 已提交
271
      end
272

273 274
      # REFERENTIAL INTEGRITY ====================================

275
      def disable_referential_integrity #:nodoc:
276 277 278 279 280 281 282 283 284
        old = select_value("SELECT @@FOREIGN_KEY_CHECKS")

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

286 287 288
      # CONNECTION MANAGEMENT ====================================

      def active?
289 290 291 292 293
        if @connection.respond_to?(:stat)
          @connection.stat
        else
          @connection.query 'select 1'
        end
294 295 296 297 298 299 300

        # mysql-ruby doesn't raise an exception when stat fails.
        if @connection.respond_to?(:errno)
          @connection.errno.zero?
        else
          true
        end
301 302 303 304 305
      rescue Mysql::Error
        false
      end

      def reconnect!
306
        disconnect!
307
        clear_cache!
308
        connect
309
      end
310

311 312 313
      def disconnect!
        @connection.close rescue nil
      end
314

315 316 317 318 319
      def reset!
        if @connection.respond_to?(:change_user)
          # See http://bugs.mysql.com/bug.php?id=33540 -- the workaround way to
          # reset the connection is to change the user to the same user.
          @connection.change_user(@config[:username], @config[:password], @config[:database])
320
          configure_connection
321 322
        end
      end
323

324
      # DATABASE STATEMENTS ======================================
325

326 327
      def select_rows(sql, name = nil)
        @connection.query_with_result = true
328
        rows = exec_without_stmt(sql, name).rows
E
Emilio Tagua 已提交
329
        @connection.more_results && @connection.next_result    # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
330 331 332
        rows
      end

333
      def clear_cache!
A
Aaron Patterson 已提交
334 335
        @statements.values.each do |cache|
          cache[:stmt].close
336 337 338 339
        end
        @statements.clear
      end

340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
      if "<3".respond_to?(:encode)
        # Taken from here:
        #   https://github.com/tmtm/ruby-mysql/blob/master/lib/mysql/charset.rb
        # Author: TOMITA Masahiro <tommy@tmtm.org>
        ENCODINGS = {
          "armscii8" => nil,
          "ascii"    => Encoding::US_ASCII,
          "big5"     => Encoding::Big5,
          "binary"   => Encoding::ASCII_8BIT,
          "cp1250"   => Encoding::Windows_1250,
          "cp1251"   => Encoding::Windows_1251,
          "cp1256"   => Encoding::Windows_1256,
          "cp1257"   => Encoding::Windows_1257,
          "cp850"    => Encoding::CP850,
          "cp852"    => Encoding::CP852,
          "cp866"    => Encoding::IBM866,
          "cp932"    => Encoding::Windows_31J,
          "dec8"     => nil,
          "eucjpms"  => Encoding::EucJP_ms,
          "euckr"    => Encoding::EUC_KR,
          "gb2312"   => Encoding::EUC_CN,
          "gbk"      => Encoding::GBK,
          "geostd8"  => nil,
          "greek"    => Encoding::ISO_8859_7,
          "hebrew"   => Encoding::ISO_8859_8,
          "hp8"      => nil,
          "keybcs2"  => nil,
          "koi8r"    => Encoding::KOI8_R,
          "koi8u"    => Encoding::KOI8_U,
          "latin1"   => Encoding::ISO_8859_1,
          "latin2"   => Encoding::ISO_8859_2,
          "latin5"   => Encoding::ISO_8859_9,
          "latin7"   => Encoding::ISO_8859_13,
          "macce"    => Encoding::MacCentEuro,
          "macroman" => Encoding::MacRoman,
          "sjis"     => Encoding::SHIFT_JIS,
          "swe7"     => nil,
          "tis620"   => Encoding::TIS_620,
          "ucs2"     => Encoding::UTF_16BE,
          "ujis"     => Encoding::EucJP_ms,
          "utf8"     => Encoding::UTF_8,
          "utf8mb4"  => Encoding::UTF_8,
        }
      else
        ENCODINGS = Hash.new { |h,k| h[k] = k }
      end

      # Get the client encoding for this database
      def client_encoding
        return @client_encoding if @client_encoding

        result = exec_query(
          "SHOW VARIABLES WHERE Variable_name = 'character_set_client'",
          'SCHEMA')
        @client_encoding = ENCODINGS[result.rows.last.last]
      end

397
      def exec_query(sql, name = 'SQL', binds = [])
398
        log(sql, name, binds) do
A
Aaron Patterson 已提交
399
          result = nil
400

A
Aaron Patterson 已提交
401
          cache = {}
402
          if binds.empty?
403 404
            stmt = @connection.prepare(sql)
          else
A
Aaron Patterson 已提交
405 406 407 408
            cache = @statements[sql] ||= {
              :stmt => @connection.prepare(sql)
            }
            stmt = cache[:stmt]
409 410
          end

411
          stmt.execute(*binds.map { |col, val|
412
            type_cast(val, col)
A
Aaron Patterson 已提交
413 414
          })
          if metadata = stmt.result_metadata
A
Aaron Patterson 已提交
415 416 417 418
            cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
              field.name
            }

419 420
            metadata.free
            result = ActiveRecord::Result.new(cols, stmt.to_a)
A
Aaron Patterson 已提交
421
          end
422

423
          stmt.free_result
424
          stmt.close if binds.empty?
425

A
Aaron Patterson 已提交
426 427 428 429
          result
        end
      end

430 431 432 433
      def exec_insert(sql, name, binds)
        exec_query(sql, name, binds)
      end

434 435 436 437
      def last_inserted_id(result)
        @connection.insert_id
      end

A
Aaron Patterson 已提交
438 439 440 441 442
      def exec_without_stmt(sql, name = 'SQL') # :nodoc:
        # Some queries, like SHOW CREATE TABLE don't work through the prepared
        # statement API.  For those queries, we need to use this method. :'(
        log(sql, name) do
          result = @connection.query(sql)
443 444 445 446 447 448 449 450
          cols = []
          rows = []

          if result
            cols = result.fetch_fields.map { |field| field.name }
            rows = result.to_a
            result.free
          end
451
          ActiveRecord::Result.new(cols, rows)
A
Aaron Patterson 已提交
452 453 454
        end
      end

455
      # Executes an SQL query and returns a MySQL::Result object. Note that you have to free
456
      # the Result object after you're done using it.
457
      def execute(sql, name = nil) #:nodoc:
458 459 460 461 462
        if name == :skip_logging
          @connection.query(sql)
        else
          log(sql, name) { @connection.query(sql) }
        end
463
      rescue ActiveRecord::StatementInvalid => exception
464 465
        if exception.message.split(":").first =~ /Packets out of order/
          raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information.  If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
466 467
        else
          raise
468
        end
D
Initial  
David Heinemeier Hansson 已提交
469
      end
470

471 472
      def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
        super sql, name
473 474
        id_value || @connection.insert_id
      end
475
      alias :create :insert_sql
476

477 478
      def update_sql(sql, name = nil) #:nodoc:
        super
479 480
        @connection.affected_rows
      end
481

482
      def begin_db_transaction #:nodoc:
483
        exec_without_stmt "BEGIN"
484
      rescue Mysql::Error
485
        # Transactions aren't supported
D
Initial  
David Heinemeier Hansson 已提交
486
      end
487

488
      def commit_db_transaction #:nodoc:
489 490 491
        execute "COMMIT"
      rescue Exception
        # Transactions aren't supported
D
Initial  
David Heinemeier Hansson 已提交
492
      end
493

494
      def rollback_db_transaction #:nodoc:
495 496 497
        execute "ROLLBACK"
      rescue Exception
        # Transactions aren't supported
D
Initial  
David Heinemeier Hansson 已提交
498
      end
499

J
Jonathan Viney 已提交
500 501 502 503 504 505 506 507 508 509 510
      def create_savepoint
        execute("SAVEPOINT #{current_savepoint_name}")
      end

      def rollback_to_savepoint
        execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
      end

      def release_savepoint
        execute("RELEASE SAVEPOINT #{current_savepoint_name}")
      end
511

512 513 514 515 516 517 518 519 520 521 522
      def add_limit_offset!(sql, options) #:nodoc:
        limit, offset = options[:limit], options[:offset]
        if limit && offset
          sql << " LIMIT #{offset.to_i}, #{sanitize_limit(limit)}"
        elsif limit
          sql << " LIMIT #{sanitize_limit(limit)}"
        elsif offset
          sql << " OFFSET #{offset.to_i}"
        end
        sql
      end
523
      deprecate :add_limit_offset!
524

525 526 527
      # SCHEMA STATEMENTS ========================================

      def structure_dump #:nodoc:
528 529 530 531 532
        if supports_views?
          sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
        else
          sql = "SHOW TABLES"
        end
533

E
Emilio Tagua 已提交
534
        select_all(sql).map do |table|
535
          table.delete('Table_type')
A
Aaron Patterson 已提交
536 537
          sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
          exec_without_stmt(sql).first['Create Table'] + ";\n\n"
E
Emilio Tagua 已提交
538
        end.join("")
539 540
      end

541
      def recreate_database(name, options = {}) #:nodoc:
D
Initial  
David Heinemeier Hansson 已提交
542
        drop_database(name)
543
        create_database(name, options)
D
Initial  
David Heinemeier Hansson 已提交
544
      end
545

546
      # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
547 548 549 550 551 552 553 554 555 556 557 558
      # Charset defaults to utf8.
      #
      # Example:
      #   create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
      #   create_database 'matt_development'
      #   create_database 'matt_development', :charset => :big5
      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
559
      end
560

561
      def drop_database(name) #:nodoc:
562
        execute "DROP DATABASE IF EXISTS `#{name}`"
D
Initial  
David Heinemeier Hansson 已提交
563
      end
564

565
      def current_database
566 567 568 569 570 571 572 573 574 575 576
        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'
577
      end
578

579
      def tables(name = nil, database = nil) #:nodoc:
580
        tables = []
581
        result = execute(["SHOW TABLES", database].compact.join(' IN '), 'SCHEMA')
582 583
        result.each { |field| tables << field[0] }
        result.free
584
        tables
D
Initial  
David Heinemeier Hansson 已提交
585
      end
586

587 588 589 590 591 592 593 594 595 596 597 598 599 600
      def table_exists?(name)
        return true if super

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

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

        tables(nil, schema).include? table
      end

601 602 603 604
      def drop_table(table_name, options = {})
        super(table_name, options)
      end

605 606 607
      def indexes(table_name, name = nil)#:nodoc:
        indexes = []
        current_index = nil
608 609
        result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
        result.each do |row|
610 611 612
          if current_index != row[2]
            next if row[2] == "PRIMARY" # skip the primary key
            current_index = row[2]
613
            indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [], [])
614 615 616
          end

          indexes.last.columns << row[4]
617
          indexes.last.lengths << row[7]
618
        end
619
        result.free
620 621 622 623
        indexes
      end

      def columns(table_name, name = nil)#:nodoc:
624
        sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
625
        columns = []
626
        result = execute(sql, 'SCHEMA')
627 628
        result.each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
        result.free
629 630 631
        columns
      end

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

636 637
      def rename_table(table_name, new_name)
        execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
638
      end
639

640 641 642 643 644 645 646 647 648 649 650 651 652 653 654
      def bulk_change_table(table_name, operations) #:nodoc:
        sqls = operations.map do |command, args|
          table, arguments = args.shift, args
          method = :"#{command}_sql"

          if respond_to?(method)
            send(method, table, *arguments)
          else
            raise "Unknown method called : #{method}(#{arguments.inspect})"
          end
        end.flatten.join(", ")

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

655
      def add_column(table_name, column_name, type, options = {})
656
        execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
657 658
      end

659
      def change_column_default(table_name, column_name, default) #:nodoc:
660 661 662 663 664 665 666 667 668 669
        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
670

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

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

678
      def rename_column(table_name, column_name, new_column_name) #:nodoc:
679
        execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
680 681
      end

682 683
      # Maps logical Rails types to MySQL-specific data types.
      def type_to_sql(type, limit = nil, precision = nil, scale = nil)
684 685 686 687 688 689 690 691 692
        return super unless type.to_s == '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}")
693 694 695
        end
      end

696 697 698 699 700 701 702
      def add_column_position!(sql, options)
        if options[:first]
          sql << " FIRST"
        elsif options[:after]
          sql << " AFTER #{quote_column_name(options[:after])}"
        end
      end
703

704 705
      # SHOW VARIABLES LIKE 'name'
      def show_variable(name)
706 707
        variables = select_all("SHOW VARIABLES LIKE '#{name}'")
        variables.first['Value'] unless variables.empty?
708 709
      end

710 711 712
      # Returns a table's primary key and belonging sequence.
      def pk_and_sequence_for(table) #:nodoc:
        keys = []
713
        result = execute("describe #{quote_table_name(table)}", 'SCHEMA')
714
        result.each_hash do |h|
715 716
          keys << h["Field"]if h["Key"] == "PRI"
        end
717
        result.free
718 719 720
        keys.length == 1 ? [keys.first, nil] : nil
      end

721 722 723 724 725 726
      # 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

727 728 729
      def case_sensitive_equality_operator
        "= BINARY"
      end
730 731 732 733 734
      deprecate :case_sensitive_equality_operator

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

736 737 738 739
      def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
        where_sql
      end

740
      protected
741 742 743 744 745 746 747 748 749 750 751 752
        def quoted_columns_for_index(column_names, options = {})
          length = options[:length] if options.is_a?(Hash)

          quoted_column_names = case length
          when Hash
            column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
          when Fixnum
            column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
          else
            column_names.map {|name| quote_column_name(name) }
          end
        end
753 754

        def translate_exception(exception, message)
755 756
          return super unless exception.respond_to?(:errno)

757 758
          case exception.errno
          when 1062
759
            RecordNotUnique.new(message, exception)
760
          when 1452
761
            InvalidForeignKey.new(message, exception)
762 763 764 765 766
          else
            super
          end
        end

767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829
        def add_column_sql(table_name, column_name, type, options = {})
          add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
          add_column_options!(add_column_sql, options)
          add_column_position!(add_column_sql, options)
          add_column_sql
        end

        def remove_column_sql(table_name, *column_names)
          columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" }
        end
        alias :remove_columns_sql :remove_column

        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

          change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
          add_column_options!(change_column_sql, options)
          add_column_position!(change_column_sql, options)
          change_column_sql
        end

        def rename_column_sql(table_name, column_name, new_column_name)
          options = {}

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

          current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
          rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
          add_column_options!(rename_column_sql, options)
          rename_column_sql
        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

D
Initial  
David Heinemeier Hansson 已提交
830
      private
831 832 833
        def connect
          encoding = @config[:encoding]
          if encoding
834
            @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
835
          end
836

837 838 839
          if @config[:sslca] || @config[:sslkey]
            @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
          end
840

841 842 843 844
          @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
          @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
          @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]

845
          @connection.real_connect(*@connection_options)
846 847 848 849

          # reconnect must be set after real_connect is called, because real_connect sets it to false internally
          @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)

850 851
          configure_connection
        end
852

853 854
        def configure_connection
          encoding = @config[:encoding]
855
          execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
856 857 858

          # By default, MySQL 'where id is null' selects the last inserted id.
          # Turn this off. http://dev.rubyonrails.org/ticket/6778
859
          execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
860 861
        end

A
Aaron Patterson 已提交
862
        def select(sql, name = nil, binds = [])
863
          @connection.query_with_result = true
864
          rows = exec_query(sql, name, binds).to_a
E
Emilio Tagua 已提交
865
          @connection.more_results && @connection.next_result    # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
D
Initial  
David Heinemeier Hansson 已提交
866 867
          rows
        end
868

869 870 871
        def supports_views?
          version[0] >= 5
        end
872

873 874 875
        def version
          @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
        end
876 877 878 879 880 881 882

        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
D
Initial  
David Heinemeier Hansson 已提交
883 884
    end
  end
885
end