abstract_mysql_adapter.rb 32.0 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
        def visit_ChangeColumnDefinition(o)
41 42
          change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}"
          add_column_position!(change_column_sql, column_options(o.column))
43 44
        end

45 46
        def add_column_position!(sql, options)
          if options[:first]
47
            sql << " FIRST"
48 49
          elsif options[:after]
            sql << " AFTER #{quote_column_name(options[:after])}"
50 51 52
          end
          sql
        end
53 54

        def index_in_create(table_name, column_name, options)
55
          index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
56
          "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
57
        end
58 59 60 61 62 63
      end

      def schema_creation
        SchemaCreation.new self
      end

64 65
      def column_spec_for_primary_key(column)
        spec = {}
66
        if column.auto_increment?
67 68 69 70 71 72 73 74 75
          return unless column.limit == 8
          spec[:id] = ':bigint'
        else
          spec[:id] = column.type.inspect
          spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) })
        end
        spec
      end

76
      class Column < ConnectionAdapters::Column # :nodoc:
S
Sean Griffin 已提交
77
        delegate :strict, :collation, :extra, to: :sql_type_metadata, allow_nil: true
78

S
Sean Griffin 已提交
79 80
        def initialize(*)
          super
81
          assert_valid_default(default)
82
          extract_default
83 84
        end

85 86 87
        def extract_default
          if blob_or_text_column?
            @default = null || strict ? nil : ''
S
Sean Griffin 已提交
88
          elsif missing_default_forged_as_empty_string?(default)
89
            @default = nil
90 91 92 93
          end
        end

        def has_default?
94
          return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
95 96
          super
        end
97

98 99 100
        def blob_or_text_column?
          sql_type =~ /blob/i || type == :text
        end
101

102 103 104 105
        def case_sensitive?
          collation && !collation.match(/_ci$/)
        end

106 107 108 109
        def auto_increment?
          extra == 'auto_increment'
        end

110 111 112 113 114 115 116 117 118 119 120 121
        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
122 123 124 125 126 127

        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
S
Sean Griffin 已提交
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
      end

      class MysqlTypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
        attr_reader :collation, :extra, :strict

        def initialize(type_metadata, collation: "", extra: "", strict: false)
          super(type_metadata)
          @type_metadata = type_metadata
          @collation = collation
          @extra = extra
          @strict = strict
        end

        def ==(other)
          other.is_a?(MysqlTypeMetadata) &&
            attributes_for_hash == other.attributes_for_hash
        end
        alias eql? ==

        def hash
          attributes_for_hash.hash
        end

        protected
152 153

        def attributes_for_hash
S
Sean Griffin 已提交
154
          [self.class, @type_metadata, collation, extra, strict]
155
        end
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
      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 = {
178
        :primary_key => "int(11) auto_increment PRIMARY KEY",
179 180 181 182 183 184 185 186 187 188 189 190
        :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 }
      }

191 192 193
      INDEX_TYPES  = [:fulltext, :spatial]
      INDEX_USINGS = [:btree, :hash]

194 195 196 197 198
      # 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 = {}, {}
199

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

202
        if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
203
          @prepared_statements = true
204
        else
A
Aaron Patterson 已提交
205 206 207 208
          @prepared_statements = false
        end
      end

209 210 211 212 213 214 215 216 217
      # Returns true, since this connection adapter supports migrations.
      def supports_migrations?
        true
      end

      def supports_primary_key?
        true
      end

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

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

228 229 230 231 232 233 234 235
      # 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

236 237 238 239
      def supports_indexes_in_create?
        true
      end

240 241 242 243
      def supports_foreign_keys?
        true
      end

244 245 246 247
      def supports_views?
        version[0] >= 5
      end

248 249 250 251
      def native_database_types
        NATIVE_DATABASE_TYPES
      end

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

256 257 258 259 260 261 262 263
      # 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

S
Sean Griffin 已提交
264 265
      def new_column(field, default, sql_type_metadata = nil, null = true) # :nodoc:
        Column.new(field, default, sql_type_metadata, null)
266 267
      end

268
      # Must return the MySQL error number from the exception, if the exception has an
269 270 271 272 273 274 275
      # error number.
      def error_number(exception) # :nodoc:
        raise NotImplementedError
      end

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

276 277 278
      def _quote(value) # :nodoc:
        if value.is_a?(Type::Binary::Data)
          "x'#{value.hex}'"
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
        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

296 297 298 299
      def unquoted_true
        1
      end

300 301 302 303
      def quoted_false
        QUOTED_FALSE
      end

304 305 306 307
      def unquoted_false
        0
      end

308 309
      # REFERENTIAL INTEGRITY ====================================

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

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

321
      #--
322
      # DATABASE STATEMENTS ======================================
323
      #++
324

325 326 327 328 329
      def clear_cache!
        super
        reload_type_map
      end

330 331
      # Executes the SQL statement in the context of this connection.
      def execute(sql, name = nil)
332
        log(sql, name) { @connection.query(sql) }
333 334 335
      end

      # MysqlAdapter has to free a result after using it, so we use this method to write
336
      # stuff in an abstract way without concerning ourselves about whether it needs to be
337 338 339 340 341 342 343 344 345 346 347 348 349 350
      # 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

351 352 353 354 355
      def begin_isolated_db_transaction(isolation)
        execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
        begin_db_transaction
      end

356 357 358 359
      def commit_db_transaction #:nodoc:
        execute "COMMIT"
      end

360
      def exec_rollback_db_transaction #:nodoc:
361 362 363 364 365
        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
366
      # these, we must use a subquery.
367 368
      def join_to_update(update, select) #:nodoc:
        if select.limit || select.offset || select.orders.any?
369
          super
370 371 372 373 374 375
        else
          update.table select.source
          update.wheres = select.constraints
        end
      end

J
Jon Leighton 已提交
376 377 378 379
      def empty_insert_statement_value
        "VALUES ()"
      end

380 381 382 383 384 385
      # 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)
386
        sql = create_database(name, options)
387
        reconnect!
388
        sql
389 390 391 392 393 394
      end

      # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
      # Charset defaults to utf8.
      #
      # Example:
A
AvnerCohen 已提交
395
      #   create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
396
      #   create_database 'matt_development'
A
AvnerCohen 已提交
397
      #   create_database 'matt_development', charset: :big5
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
      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

428 429
      def tables(name = nil, database = nil, like = nil) #:nodoc:
        sql = "SHOW TABLES "
430
        sql << "IN #{quote_table_name(database)} " if database
431
        sql << "LIKE #{quote(like)}" if like
432 433

        execute_and_free(sql, 'SCHEMA') do |result|
434
          result.collect(&:first)
435 436 437
        end
      end

438 439 440 441
      def truncate(table_name, name = nil)
        execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
      end

442
      def table_exists?(name)
443
        return false unless name.present?
444
        return true if tables(nil, nil, name).any?
445 446 447 448 449 450 451 452 453

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

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

454
        tables(nil, schema, table).any?
455 456 457 458 459 460 461 462 463 464 465
      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]
466 467 468 469 470

              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)
471 472 473 474 475 476 477 478 479 480 481
            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+.
482
      def columns(table_name)#:nodoc:
483
        sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
484 485
        execute_and_free(sql, 'SCHEMA') do |result|
          each_hash(result).map do |field|
486
            field_name = set_field_encoding(field[:Field])
487
            sql_type = field[:Type]
S
Sean Griffin 已提交
488 489
            type_metadata = fetch_type_metadata(sql_type, field[:Collation], field[:Extra])
            new_column(field_name, field[:Default], type_metadata, field[:Null] == "YES")
490 491 492 493 494 495 496 497
          end
        end
      end

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

498
      def bulk_change_table(table_name, operations) #:nodoc:
499
        sqls = operations.flat_map do |command, args|
500 501 502
          table, arguments = args.shift, args
          method = :"#{command}_sql"

503
          if respond_to?(method, true)
504 505 506 507
            send(method, table, *arguments)
          else
            raise "Unknown method called : #{method}(#{arguments.inspect})"
          end
508
        end.join(", ")
509 510 511 512

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

513 514 515 516 517 518
      # 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)}"
519
        rename_table_indexes(table_name, new_name)
520 521
      end

522
      def drop_table(table_name, options = {})
523
        execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
524 525
      end

526
      def rename_index(table_name, old_name, new_name)
527
        if supports_rename_index?
528 529
          validate_index_length!(table_name, new_name)

530 531 532 533 534 535
          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 已提交
536
      def change_column_default(table_name, column_name, default) #:nodoc:
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
        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)}")
557
        rename_column_indexes(table_name, column_name, new_column_name)
558 559
      end

D
doabit 已提交
560
      def add_index(table_name, column_name, options = {}) #:nodoc:
561 562
        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 已提交
563 564
      end

565
      def foreign_keys(table_name)
566
        fk_info = select_all <<-SQL.strip_heredoc
567 568 569 570 571 572 573 574
          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}'
575
        SQL
576 577 578 579

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

        fk_info.map do |row|
580 581 582 583 584 585
          options = {
            column: row['column'],
            name: row['name'],
            primary_key: row['primary_key']
          }

Y
Yves Senn 已提交
586 587
          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")
588

589 590 591 592
          ForeignKeyDefinition.new(table_name, row['to_table'], options)
        end
      end

593 594
      # Maps logical Rails types to MySQL-specific data types.
      def type_to_sql(type, limit = nil, precision = nil, scale = nil)
595
        case type.to_s
596 597 598 599 600 601 602
        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
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619
        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
620 621 622 623 624
        when 'datetime'
          return super unless precision

          case precision
            when 0..6; "datetime(#{precision})"
625
            else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.")
626
          end
627 628
        else
          super
629 630 631 632 633
        end
      end

      # SHOW VARIABLES LIKE 'name'
      def show_variable(name)
K
kennyj 已提交
634
        variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA')
635 636 637 638 639
        variables.first['Value'] unless variables.empty?
      end

      # Returns a table's primary key and belonging sequence.
      def pk_and_sequence_for(table)
K
kennyj 已提交
640 641
        execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
          create_table = each_hash(result).first[:"Create Table"]
642
          if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
S
Seamus Abshere 已提交
643
            keys = $1.split(",").map { |key| key.delete('`"') }
K
kennyj 已提交
644 645 646 647
            keys.length == 1 ? [keys.first, nil] : nil
          else
            nil
          end
648 649 650 651 652 653 654 655 656
        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

657 658
      def case_sensitive_modifier(node, table_attribute)
        node = Arel::Nodes.build_quoted node, table_attribute
659 660 661
        Arel::Nodes::Bin.new(node)
      end

662 663 664 665 666 667 668 669
      def case_sensitive_comparison(table, attribute, column, value)
        if column.case_sensitive?
          table[attribute].eq(value)
        else
          super
        end
      end

670 671 672 673 674 675 676 677
      def case_insensitive_comparison(table, attribute, column, value)
        if column.case_sensitive?
          super
        else
          table[attribute].eq(value)
        end
      end

678
      def strict_mode?
679
        self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
680 681
      end

682 683 684 685
      def valid_type?(type)
        !native_database_types[type].nil?
      end

686 687
      protected

S
Sean Griffin 已提交
688
      def initialize_type_map(m) # :nodoc:
689
        super
690

691 692
        register_class_with_limit m, %r(char)i, MysqlString

693 694 695 696 697 698
        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)
699 700
        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 已提交
701 702 703
        m.register_type %r(^float)i,     Type::Float.new(limit: 24)
        m.register_type %r(^double)i,    Type::Float.new(limit: 53)

704 705 706 707 708 709
        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

710 711 712 713
        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'
714

715 716
        m.register_type(%r(datetime)i) do |sql_type|
          precision = extract_precision(sql_type)
717
          MysqlDateTime.new(precision: precision)
718 719
        end

720 721 722
        m.register_type(%r(enum)i) do |sql_type|
          limit = sql_type[/^enum\((.+)\)/i, 1]
            .split(',').map{|enum| enum.strip.length - 2}.max
723
          MysqlString.new(limit: limit)
724
        end
725 726
      end

727 728 729 730 731 732 733 734 735 736
      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

S
Sean Griffin 已提交
737 738 739 740
      def fetch_type_metadata(sql_type, collation = "", extra = "")
        MysqlTypeMetadata.new(super(sql_type), collation: collation, extra: extra, strict: strict_mode?)
      end

741 742 743 744 745 746 747 748 749 750 751
      # 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

752 753 754 755
      def add_index_length(option_strings, column_names, options = {})
        if options.is_a?(Hash) && length = options[:length]
          case length
          when Hash
756
            column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
757 758 759 760 761 762 763 764
          when Fixnum
            column_names.each {|name| option_strings[name] += "(#{length})"}
          end
        end

        return option_strings
      end

765
      def quoted_columns_for_index(column_names, options = {})
766
        option_strings = Hash[column_names.map {|name| [name, '']}]
767

768 769 770 771 772 773 774
        # 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]}
775 776 777 778 779 780 781 782 783 784 785 786 787 788
      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 = {})
789
        td = create_table_definition(table_name)
790 791
        cd = td.new_column_definition(column_name, type, options)
        schema_creation.visit_AddColumn cd
792 793 794 795 796 797 798 799 800 801 802 803 804
      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

805 806 807
        td = create_table_definition(table_name)
        cd = td.new_column_definition(column.name, type, options)
        schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
808 809 810
      end

      def rename_column_sql(table_name, column_name, new_column_name)
811
        column  = column_for(table_name, column_name)
812 813 814
        options = {
          default: column.default,
          null: column.null,
815
          auto_increment: column.auto_increment?
816
        }
817

K
kennyj 已提交
818
        current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
819 820 821
        td = create_table_definition(table_name)
        cd = td.new_column_definition(new_column_name, current_type, options)
        schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
822 823
      end

M
Marc-Andre Lafortune 已提交
824 825 826 827 828 829
      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) }
830 831 832 833 834 835 836 837 838 839 840 841
      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

842 843
      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)]
844 845
      end

846
      def remove_timestamps_sql(table_name, options = {})
847 848 849
        [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
      end

850 851
      private

852
      def version
853
        @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map(&:to_i)
854 855 856
      end

      def mariadb?
857
        full_version =~ /mariadb/i
858 859 860 861 862 863
      end

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

864
      def configure_connection
865
        variables = @config.fetch(:variables, {}).stringify_keys
866 867 868

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

        # Increase timeout so the server doesn't disconnect us.
        wait_timeout = @config[:wait_timeout]
        wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
874
        variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
875 876 877 878

        # 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.
879 880
        unless variables.has_key?('sql_mode')
          variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
881 882 883 884 885
        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)
886 887 888 889 890
        if @config[:encoding]
          encoding = "NAMES #{@config[:encoding]}"
          encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
          encoding << ", "
        end
891 892 893 894

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

        # ...and send them all in one query
903
        @connection.query  "SET #{encoding} #{variable_assignments}"
904
      end
905 906 907 908 909 910 911 912 913

      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
914

915
      def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
916 917 918
        TableDefinition.new(native_database_types, name, temporary, options, as)
      end

919 920 921 922 923 924
      class MysqlDateTime < Type::DateTime # :nodoc:
        def type_cast_for_database(value)
          if value.acts_like?(:time) && value.respond_to?(:usec)
            result = super.to_s(:db)
            case precision
            when 1..6
925
              "#{result}.#{sprintf("%0#{precision}d", value.usec / 10 ** (6 - precision))}"
926 927 928 929 930 931 932 933 934
            else
              result
            end
          else
            super
          end
        end
      end

935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953
      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
954 955 956 957

      def type_classes_with_standard_constructor
        super.merge(string: MysqlString, date_time: MysqlDateTime)
      end
958 959 960
    end
  end
end