abstract_mysql_adapter.rb 30.7 KB
Newer Older
1
require 'arel/visitors/bind_visitor'
2
require 'active_support/core_ext/string/strip'
3 4 5 6

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

9 10 11 12 13 14 15
      class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
        def primary_key(name, type = :primary_key, options = {})
          options[:auto_increment] ||= type == :bigint
          super
        end
      end

16
      class SchemaCreation < AbstractAdapter::SchemaCreation
R
Rafael Mendonça França 已提交
17 18 19 20
        def visit_AddColumn(o)
          add_column_position!(super, column_options(o))
        end

21 22
        private

23 24 25 26
        def visit_DropForeignKey(name)
          "DROP FOREIGN KEY #{name}"
        end

27
        def visit_TableDefinition(o)
R
Rafael Mendonça França 已提交
28 29 30 31 32 33
          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) })

34
          create_sql << "(#{statements.join(', ')}) " if statements.present?
35 36 37 38
          create_sql << "#{o.options}"
          create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
          create_sql
        end
39

40 41 42 43 44
        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}"
L
Luke Steensen 已提交
45
          add_column_options!(change_column_sql, options.merge(column: column))
46
          add_column_position!(change_column_sql, options)
47 48
        end

49 50
        def add_column_position!(sql, options)
          if options[:first]
51
            sql << " FIRST"
52 53
          elsif options[:after]
            sql << " AFTER #{quote_column_name(options[:after])}"
54 55 56
          end
          sql
        end
57 58

        def index_in_create(table_name, column_name, options)
59
          index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
60
          "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
61
        end
62 63 64 65 66 67
      end

      def schema_creation
        SchemaCreation.new self
      end

68
      class Column < ConnectionAdapters::Column # :nodoc:
69
        attr_reader :collation, :strict, :extra
70

71
        def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
72
          @strict    = strict
73
          @collation = collation
74
          @extra     = extra
75
          super(name, default, cast_type, sql_type, null)
76
          assert_valid_default(default)
77
          extract_default
78 79
        end

80 81 82 83 84
        def extract_default
          if blob_or_text_column?
            @default = null || strict ? nil : ''
          elsif missing_default_forged_as_empty_string?(@default)
            @default = nil
85 86 87 88
          end
        end

        def has_default?
89
          return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
90 91
          super
        end
92

93 94 95
        def blob_or_text_column?
          sql_type =~ /blob/i || type == :text
        end
96

97 98 99 100
        def case_sensitive?
          collation && !collation.match(/_ci$/)
        end

101 102 103 104 105 106 107
        def ==(other)
          super &&
            collation == other.collation &&
            strict == other.strict &&
            extra == other.extra
        end

108 109 110 111 112 113 114 115 116 117 118 119
        private

        # 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
120 121 122 123 124 125

        def assert_valid_default(default)
          if blob_or_text_column? && default.present?
            raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
          end
        end
126 127 128 129

        def attributes_for_hash
          super + [collation, strict, extra]
        end
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
      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 = {
152
        :primary_key => "int(11) auto_increment PRIMARY KEY",
153 154 155 156 157 158 159 160 161 162 163 164
        :string      => { :name => "varchar", :limit => 255 },
        :text        => { :name => "text" },
        :integer     => { :name => "int", :limit => 4 },
        :float       => { :name => "float" },
        :decimal     => { :name => "decimal" },
        :datetime    => { :name => "datetime" },
        :time        => { :name => "time" },
        :date        => { :name => "date" },
        :binary      => { :name => "blob" },
        :boolean     => { :name => "tinyint", :limit => 1 }
      }

165 166 167
      INDEX_TYPES  = [:fulltext, :spatial]
      INDEX_USINGS = [:btree, :hash]

168 169 170 171 172
      # 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 = {}, {}
173

A
Aaron Patterson 已提交
174 175
        @visitor = Arel::Visitors::MySQL.new self

176
        if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
177
          @prepared_statements = true
178
        else
A
Aaron Patterson 已提交
179 180 181 182
          @prepared_statements = false
        end
      end

183 184 185 186 187 188 189 190 191
      # Returns true, since this connection adapter supports migrations.
      def supports_migrations?
        true
      end

      def supports_primary_key?
        true
      end

192 193 194 195
      def supports_bulk_alter? #:nodoc:
        true
      end

196
      # Technically MySQL allows to create indexes with the sort order syntax
197 198 199 200 201
      # but at the moment (5.5) it doesn't yet implement them
      def supports_index_sort_order?
        true
      end

202 203 204 205 206 207 208 209
      # 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

210 211 212 213
      def supports_indexes_in_create?
        true
      end

214 215 216 217
      def supports_foreign_keys?
        true
      end

218 219 220 221
      def supports_views?
        version[0] >= 5
      end

222 223 224 225
      def native_database_types
        NATIVE_DATABASE_TYPES
      end

226 227 228 229
      def index_algorithms
        { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
      end

230 231 232 233 234 235 236 237
      # 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

238
      def new_column(field, default, cast_type, sql_type = nil, null = true, collation = "", extra = "") # :nodoc:
239
        Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)
240 241
      end

242
      # Must return the MySQL error number from the exception, if the exception has an
243 244 245 246 247 248 249
      # error number.
      def error_number(exception) # :nodoc:
        raise NotImplementedError
      end

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

250 251 252
      def _quote(value) # :nodoc:
        if value.is_a?(Type::Binary::Data)
          "x'#{value.hex}'"
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
        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

270 271 272 273
      def unquoted_true
        1
      end

274 275 276 277
      def quoted_false
        QUOTED_FALSE
      end

278 279 280 281
      def unquoted_false
        0
      end

282 283
      # REFERENTIAL INTEGRITY ====================================

R
Rafael Mendonça França 已提交
284
      def disable_referential_integrity #:nodoc:
285 286 287 288 289 290 291 292 293 294
        old = select_value("SELECT @@FOREIGN_KEY_CHECKS")

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

295
      #--
296
      # DATABASE STATEMENTS ======================================
297
      #++
298

299 300 301 302 303
      def clear_cache!
        super
        reload_type_map
      end

304 305
      # Executes the SQL statement in the context of this connection.
      def execute(sql, name = nil)
306
        log(sql, name) { @connection.query(sql) }
307 308 309
      end

      # MysqlAdapter has to free a result after using it, so we use this method to write
310
      # stuff in an abstract way without concerning ourselves about whether it needs to be
311 312 313 314 315 316 317 318 319 320 321 322 323 324
      # 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

325 326 327 328 329
      def begin_isolated_db_transaction(isolation)
        execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
        begin_db_transaction
      end

330 331 332 333 334 335 336 337 338 339
      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
340
      # these, we must use a subquery.
341 342
      def join_to_update(update, select) #:nodoc:
        if select.limit || select.offset || select.orders.any?
343
          super
344 345 346 347 348 349
        else
          update.table select.source
          update.wheres = select.constraints
        end
      end

J
Jon Leighton 已提交
350 351 352 353
      def empty_insert_statement_value
        "VALUES ()"
      end

354 355 356 357 358 359
      # 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)
360
        sql = create_database(name, options)
361
        reconnect!
362
        sql
363 364 365 366 367 368
      end

      # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
      # Charset defaults to utf8.
      #
      # Example:
A
AvnerCohen 已提交
369
      #   create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
370
      #   create_database 'matt_development'
A
AvnerCohen 已提交
371
      #   create_database 'matt_development', charset: :big5
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 397 398 399 400 401
      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

402 403
      def tables(name = nil, database = nil, like = nil) #:nodoc:
        sql = "SHOW TABLES "
404
        sql << "IN #{quote_table_name(database)} " if database
405
        sql << "LIKE #{quote(like)}" if like
406 407

        execute_and_free(sql, 'SCHEMA') do |result|
408
          result.collect(&:first)
409 410 411
        end
      end

412 413 414 415
      def truncate(table_name, name = nil)
        execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
      end

416
      def table_exists?(name)
417
        return false unless name.present?
418
        return true if tables(nil, nil, name).any?
419 420 421 422 423 424 425 426 427

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

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

428
        tables(nil, schema, table).any?
429 430 431 432 433 434 435 436 437 438 439
      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]
440 441 442 443 444

              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)
445 446 447 448 449 450 451 452 453 454 455
            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+.
456
      def columns(table_name)#:nodoc:
457
        sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
458 459
        execute_and_free(sql, 'SCHEMA') do |result|
          each_hash(result).map do |field|
460
            field_name = set_field_encoding(field[:Field])
461 462 463
            sql_type = field[:Type]
            cast_type = lookup_cast_type(sql_type)
            new_column(field_name, field[:Default], cast_type, sql_type, field[:Null] == "YES", field[:Collation], field[:Extra])
464 465 466 467 468 469 470 471
          end
        end
      end

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

472
      def bulk_change_table(table_name, operations) #:nodoc:
473
        sqls = operations.flat_map do |command, args|
474 475 476
          table, arguments = args.shift, args
          method = :"#{command}_sql"

477
          if respond_to?(method, true)
478 479 480 481
            send(method, table, *arguments)
          else
            raise "Unknown method called : #{method}(#{arguments.inspect})"
          end
482
        end.join(", ")
483 484 485 486

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

487 488 489 490 491 492
      # 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)}"
493
        rename_table_indexes(table_name, new_name)
494 495
      end

496
      def drop_table(table_name, options = {})
497
        execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
498 499
      end

500
      def rename_index(table_name, old_name, new_name)
501
        if supports_rename_index?
502 503
          validate_index_length!(table_name, new_name)

504 505 506 507 508 509
          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

T
Tony Miller 已提交
510
      def change_column_default(table_name, column_name, default) #:nodoc:
511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530
        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)}")
531
        rename_column_indexes(table_name, column_name, new_column_name)
532 533
      end

D
doabit 已提交
534
      def add_index(table_name, column_name, options = {}) #:nodoc:
535 536
        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 已提交
537 538
      end

539
      def foreign_keys(table_name)
540
        fk_info = select_all <<-SQL.strip_heredoc
541 542 543 544 545 546 547 548
          SELECT fk.referenced_table_name as 'to_table'
                ,fk.referenced_column_name as 'primary_key'
                ,fk.column_name as 'column'
                ,fk.constraint_name as 'name'
          FROM information_schema.key_column_usage fk
          WHERE fk.referenced_column_name is not null
            AND fk.table_schema = '#{@config[:database]}'
            AND fk.table_name = '#{table_name}'
549
        SQL
550 551 552 553

        create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]

        fk_info.map do |row|
554 555 556 557 558 559
          options = {
            column: row['column'],
            name: row['name'],
            primary_key: row['primary_key']
          }

Y
Yves Senn 已提交
560 561
          options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE")
          options[:on_delete] = extract_foreign_key_action(create_table_info, row['name'], "DELETE")
562

563 564 565 566
          ForeignKeyDefinition.new(table_name, row['to_table'], options)
        end
      end

567 568
      # Maps logical Rails types to MySQL-specific data types.
      def type_to_sql(type, limit = nil, precision = nil, scale = nil)
569
        case type.to_s
570 571 572 573 574 575 576
        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
577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
        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
594 595 596 597 598 599 600
        when 'datetime'
          return super unless precision

          case precision
            when 0..6; "datetime(#{precision})"
            else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6")
          end
601 602
        else
          super
603 604 605 606 607
        end
      end

      # SHOW VARIABLES LIKE 'name'
      def show_variable(name)
K
kennyj 已提交
608
        variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA')
609 610 611 612 613
        variables.first['Value'] unless variables.empty?
      end

      # Returns a table's primary key and belonging sequence.
      def pk_and_sequence_for(table)
K
kennyj 已提交
614 615
        execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
          create_table = each_hash(result).first[:"Create Table"]
616
          if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
S
Seamus Abshere 已提交
617
            keys = $1.split(",").map { |key| key.delete('`"') }
K
kennyj 已提交
618 619 620 621
            keys.length == 1 ? [keys.first, nil] : nil
          else
            nil
          end
622 623 624 625 626 627 628 629 630
        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

631 632
      def case_sensitive_modifier(node, table_attribute)
        node = Arel::Nodes.build_quoted node, table_attribute
633 634 635
        Arel::Nodes::Bin.new(node)
      end

636 637 638 639 640 641 642 643
      def case_sensitive_comparison(table, attribute, column, value)
        if column.case_sensitive?
          table[attribute].eq(value)
        else
          super
        end
      end

644 645 646 647 648 649 650 651
      def case_insensitive_comparison(table, attribute, column, value)
        if column.case_sensitive?
          super
        else
          table[attribute].eq(value)
        end
      end

652
      def strict_mode?
653
        self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
654 655
      end

656 657 658 659
      def valid_type?(type)
        !native_database_types[type].nil?
      end

660 661
      protected

S
Sean Griffin 已提交
662
      def initialize_type_map(m) # :nodoc:
663
        super
664

665 666
        register_class_with_limit m, %r(char)i, MysqlString

667 668 669 670 671 672
        m.register_type %r(tinytext)i,   Type::Text.new(limit: 2**8 - 1)
        m.register_type %r(tinyblob)i,   Type::Binary.new(limit: 2**8 - 1)
        m.register_type %r(text)i,       Type::Text.new(limit: 2**16 - 1)
        m.register_type %r(blob)i,       Type::Binary.new(limit: 2**16 - 1)
        m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
        m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
673 674
        m.register_type %r(longtext)i,   Type::Text.new(limit: 2**32 - 1)
        m.register_type %r(longblob)i,   Type::Binary.new(limit: 2**32 - 1)
S
Sean Griffin 已提交
675 676 677
        m.register_type %r(^float)i,     Type::Float.new(limit: 24)
        m.register_type %r(^double)i,    Type::Float.new(limit: 53)

678 679 680 681 682 683
        register_integer_type m, %r(^bigint)i,    limit: 8
        register_integer_type m, %r(^int)i,       limit: 4
        register_integer_type m, %r(^mediumint)i, limit: 3
        register_integer_type m, %r(^smallint)i,  limit: 2
        register_integer_type m, %r(^tinyint)i,   limit: 1

684 685 686 687
        m.alias_type %r(tinyint\(1\))i,  'boolean' if emulate_booleans
        m.alias_type %r(set)i,           'varchar'
        m.alias_type %r(year)i,          'integer'
        m.alias_type %r(bit)i,           'binary'
688

689 690 691 692 693
        m.register_type(%r(datetime)i) do |sql_type|
          precision = extract_precision(sql_type)
          Type::DateTime.new(precision: precision)
        end

694 695 696
        m.register_type(%r(enum)i) do |sql_type|
          limit = sql_type[/^enum\((.+)\)/i, 1]
            .split(',').map{|enum| enum.strip.length - 2}.max
697
          MysqlString.new(limit: limit)
698
        end
699 700
      end

701 702 703 704 705 706 707 708 709 710
      def register_integer_type(mapping, key, options) # :nodoc:
        mapping.register_type(key) do |sql_type|
          if /unsigned/i =~ sql_type
            Type::UnsignedInteger.new(options)
          else
            Type::Integer.new(options)
          end
        end
      end

711 712 713 714 715 716 717 718 719 720 721
      # 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

722 723 724 725
      def add_index_length(option_strings, column_names, options = {})
        if options.is_a?(Hash) && length = options[:length]
          case length
          when Hash
726
            column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
727 728 729 730 731 732 733 734
          when Fixnum
            column_names.each {|name| option_strings[name] += "(#{length})"}
          end
        end

        return option_strings
      end

735
      def quoted_columns_for_index(column_names, options = {})
736
        option_strings = Hash[column_names.map {|name| [name, '']}]
737

738 739 740 741 742 743 744
        # 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]}
745 746 747 748 749 750 751 752 753 754 755 756 757 758
      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 = {})
759 760 761
        td = create_table_definition table_name, options[:temporary], options[:options]
        cd = td.new_column_definition(column_name, type, options)
        schema_creation.visit_AddColumn cd
762 763 764 765 766 767 768 769 770 771 772 773 774
      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

775 776
        options[:name] = column.name
        schema_creation.accept ChangeColumnDefinition.new column, type, options
777 778 779
      end

      def rename_column_sql(table_name, column_name, new_column_name)
780
        column  = column_for(table_name, column_name)
781 782 783 784 785 786
        options = {
          name: new_column_name,
          default: column.default,
          null: column.null,
          auto_increment: column.extra == "auto_increment"
        }
787

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

M
Marc-Andre Lafortune 已提交
792 793 794 795 796 797
      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) }
798 799 800 801 802 803 804 805 806 807 808 809
      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

810 811
      def add_timestamps_sql(table_name, options = {})
        [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]
812 813
      end

814
      def remove_timestamps_sql(table_name, options = {})
815 816 817
        [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
      end

818 819
      private

820
      def version
821
        @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map(&:to_i)
822 823 824
      end

      def mariadb?
825
        full_version =~ /mariadb/i
826 827 828 829 830 831
      end

      def supports_rename_index?
        mariadb? ? false : (version[0] == 5 && version[1] >= 7) || version[0] >= 6
      end

832
      def configure_connection
833
        variables = @config.fetch(:variables, {}).stringify_keys
834 835 836

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

        # Increase timeout so the server doesn't disconnect us.
        wait_timeout = @config[:wait_timeout]
        wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
842
        variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
843 844 845 846

        # 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.
847 848
        unless variables.has_key?('sql_mode')
          variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
849 850 851 852 853
        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)
854 855 856 857 858
        if @config[:encoding]
          encoding = "NAMES #{@config[:encoding]}"
          encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
          encoding << ", "
        end
859 860 861 862

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

        # ...and send them all in one query
871
        @connection.query  "SET #{encoding} #{variable_assignments}"
872
      end
873 874 875 876 877 878 879 880 881

      def extract_foreign_key_action(structure, name, action) # :nodoc:
        if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/
          case $1
          when 'CASCADE'; :cascade
          when 'SET NULL'; :nullify
          end
        end
      end
882

883 884 885 886
      def create_table_definition(name, temporary, options, as = nil) # :nodoc:
        TableDefinition.new(native_database_types, name, temporary, options, as)
      end

887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905
      class MysqlString < Type::String # :nodoc:
        def type_cast_for_database(value)
          case value
          when true then "1"
          when false then "0"
          else super
          end
        end

        private

        def cast_value(value)
          case value
          when true then "1"
          when false then "0"
          else super
          end
        end
      end
906 907 908
    end
  end
end