abstract_mysql_adapter.rb 32.1 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
      module ColumnMethods
        def primary_key(name, type = :primary_key, **options)
          options[:auto_increment] = true if type == :bigint
12 13
          super
        end
14 15 16 17
      end

      class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
        include ColumnMethods
18 19 20 21 22 23 24 25 26 27

        def new_column_definition(name, type, options) # :nodoc:
          column = super
          case column.type
          when :primary_key
            column.type = :integer
            column.auto_increment = true
          end
          column
        end
28 29
      end

30 31 32 33
      class Table < ActiveRecord::ConnectionAdapters::Table
        include ColumnMethods
      end

34
      class SchemaCreation < AbstractAdapter::SchemaCreation
R
Rafael Mendonça França 已提交
35 36 37 38
        def visit_AddColumn(o)
          add_column_position!(super, column_options(o))
        end

39 40
        private

41 42 43 44
        def visit_DropForeignKey(name)
          "DROP FOREIGN KEY #{name}"
        end

45
        def visit_TableDefinition(o)
R
Rafael Mendonça França 已提交
46 47 48 49 50 51
          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) })

52
          create_sql << "(#{statements.join(', ')}) " if statements.present?
53 54 55 56
          create_sql << "#{o.options}"
          create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
          create_sql
        end
57

58
        def visit_ChangeColumnDefinition(o)
59 60
          change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}"
          add_column_position!(change_column_sql, column_options(o.column))
61 62
        end

63 64
        def add_column_position!(sql, options)
          if options[:first]
65
            sql << " FIRST"
66 67
          elsif options[:after]
            sql << " AFTER #{quote_column_name(options[:after])}"
68 69 70
          end
          sql
        end
71 72

        def index_in_create(table_name, column_name, options)
73
          index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
74
          "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
75
        end
76 77
      end

78 79 80 81
      def update_table_definition(table_name, base) # :nodoc:
        Table.new(table_name, base)
      end

82 83 84 85
      def schema_creation
        SchemaCreation.new self
      end

86 87
      def column_spec_for_primary_key(column)
        spec = {}
88
        if column.auto_increment?
89 90 91 92 93 94 95 96 97
          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

98 99
      def prepare_column_options(column)
        spec = super
100
        spec.delete(:precision) if /time/ === column.sql_type && column.precision == 0
101
        spec.delete(:limit)     if :boolean === column.type
102 103 104
        spec
      end

105
      class Column < ConnectionAdapters::Column # :nodoc:
S
Sean Griffin 已提交
106
        delegate :strict, :collation, :extra, to: :sql_type_metadata, allow_nil: true
107

S
Sean Griffin 已提交
108 109
        def initialize(*)
          super
110
          assert_valid_default(default)
111
          extract_default
112 113
        end

114 115 116
        def extract_default
          if blob_or_text_column?
            @default = null || strict ? nil : ''
S
Sean Griffin 已提交
117
          elsif missing_default_forged_as_empty_string?(default)
118
            @default = nil
119 120 121 122
          end
        end

        def has_default?
123
          return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
124 125
          super
        end
126

127 128 129
        def blob_or_text_column?
          sql_type =~ /blob/i || type == :text
        end
130

131 132 133 134
        def case_sensitive?
          collation && !collation.match(/_ci$/)
        end

135 136 137 138
        def auto_increment?
          extra == 'auto_increment'
        end

139 140 141 142 143 144 145 146 147 148 149 150
        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
151 152 153 154 155 156

        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 已提交
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
      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
181 182

        def attributes_for_hash
S
Sean Griffin 已提交
183
          [self.class, @type_metadata, collation, extra, strict]
184
        end
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
      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 = {
207
        :primary_key => "int(11) auto_increment PRIMARY KEY",
208 209 210 211 212 213 214 215 216 217 218 219
        :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 }
      }

220 221 222
      INDEX_TYPES  = [:fulltext, :spatial]
      INDEX_USINGS = [:btree, :hash]

223 224 225 226 227
      # 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 = {}, {}
228

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

231
        if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
232
          @prepared_statements = true
233
        else
A
Aaron Patterson 已提交
234 235 236 237
          @prepared_statements = false
        end
      end

238 239 240 241 242 243 244 245 246
      # Returns true, since this connection adapter supports migrations.
      def supports_migrations?
        true
      end

      def supports_primary_key?
        true
      end

247 248 249 250
      def supports_bulk_alter? #:nodoc:
        true
      end

251
      # Technically MySQL allows to create indexes with the sort order syntax
252 253 254 255 256
      # but at the moment (5.5) it doesn't yet implement them
      def supports_index_sort_order?
        true
      end

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

265 266 267 268
      def supports_indexes_in_create?
        true
      end

269 270 271 272
      def supports_foreign_keys?
        true
      end

273 274 275 276
      def supports_views?
        version[0] >= 5
      end

277 278 279 280
      def supports_datetime_with_precision?
        (version[0] == 5 && version[1] >= 6) || version[0] >= 6
      end

281 282 283 284
      def native_database_types
        NATIVE_DATABASE_TYPES
      end

285 286 287 288
      def index_algorithms
        { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
      end

289 290 291 292 293 294 295 296
      # 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 已提交
297 298
      def new_column(field, default, sql_type_metadata = nil, null = true) # :nodoc:
        Column.new(field, default, sql_type_metadata, null)
299 300
      end

301
      # Must return the MySQL error number from the exception, if the exception has an
302 303 304 305 306 307 308
      # error number.
      def error_number(exception) # :nodoc:
        raise NotImplementedError
      end

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

309 310 311
      def _quote(value) # :nodoc:
        if value.is_a?(Type::Binary::Data)
          "x'#{value.hex}'"
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
        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

329 330 331 332
      def unquoted_true
        1
      end

333 334 335 336
      def quoted_false
        QUOTED_FALSE
      end

337 338 339 340
      def unquoted_false
        0
      end

341 342
      # REFERENTIAL INTEGRITY ====================================

R
Rafael Mendonça França 已提交
343
      def disable_referential_integrity #:nodoc:
344 345 346 347 348 349 350 351 352 353
        old = select_value("SELECT @@FOREIGN_KEY_CHECKS")

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

354
      #--
355
      # DATABASE STATEMENTS ======================================
356
      #++
357

358 359 360 361 362
      def clear_cache!
        super
        reload_type_map
      end

363 364
      # Executes the SQL statement in the context of this connection.
      def execute(sql, name = nil)
365
        log(sql, name) { @connection.query(sql) }
366 367 368
      end

      # MysqlAdapter has to free a result after using it, so we use this method to write
369
      # stuff in an abstract way without concerning ourselves about whether it needs to be
370 371 372 373 374 375 376 377 378 379 380 381 382 383
      # 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

384 385 386 387 388
      def begin_isolated_db_transaction(isolation)
        execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
        begin_db_transaction
      end

389 390 391 392
      def commit_db_transaction #:nodoc:
        execute "COMMIT"
      end

393
      def exec_rollback_db_transaction #:nodoc:
394 395 396 397 398
        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
399
      # these, we must use a subquery.
400 401
      def join_to_update(update, select) #:nodoc:
        if select.limit || select.offset || select.orders.any?
402
          super
403 404 405 406 407 408
        else
          update.table select.source
          update.wheres = select.constraints
        end
      end

J
Jon Leighton 已提交
409 410 411 412
      def empty_insert_statement_value
        "VALUES ()"
      end

413 414 415 416 417 418
      # 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)
419
        sql = create_database(name, options)
420
        reconnect!
421
        sql
422 423 424 425 426 427
      end

      # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
      # Charset defaults to utf8.
      #
      # Example:
A
AvnerCohen 已提交
428
      #   create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
429
      #   create_database 'matt_development'
A
AvnerCohen 已提交
430
      #   create_database 'matt_development', charset: :big5
431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
      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

461 462
      def tables(name = nil, database = nil, like = nil) #:nodoc:
        sql = "SHOW TABLES "
463
        sql << "IN #{quote_table_name(database)} " if database
464
        sql << "LIKE #{quote(like)}" if like
465 466

        execute_and_free(sql, 'SCHEMA') do |result|
467
          result.collect(&:first)
468 469 470
        end
      end

471 472 473 474
      def truncate(table_name, name = nil)
        execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
      end

475
      def table_exists?(name)
476
        return false unless name.present?
477
        return true if tables(nil, nil, name).any?
478 479 480 481 482 483 484 485 486

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

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

487
        tables(nil, schema, table).any?
488 489 490 491 492 493 494 495 496 497 498
      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]
499 500 501 502 503

              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)
504 505 506 507 508 509 510 511 512 513 514
            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+.
515
      def columns(table_name)#:nodoc:
516
        sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
517 518
        execute_and_free(sql, 'SCHEMA') do |result|
          each_hash(result).map do |field|
519
            field_name = set_field_encoding(field[:Field])
520
            sql_type = field[:Type]
S
Sean Griffin 已提交
521 522
            type_metadata = fetch_type_metadata(sql_type, field[:Collation], field[:Extra])
            new_column(field_name, field[:Default], type_metadata, field[:Null] == "YES")
523 524 525 526 527 528 529 530
          end
        end
      end

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

531
      def bulk_change_table(table_name, operations) #:nodoc:
532
        sqls = operations.flat_map do |command, args|
533 534 535
          table, arguments = args.shift, args
          method = :"#{command}_sql"

536
          if respond_to?(method, true)
537 538 539 540
            send(method, table, *arguments)
          else
            raise "Unknown method called : #{method}(#{arguments.inspect})"
          end
541
        end.join(", ")
542 543 544 545

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

546 547 548 549 550 551
      # 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)}"
552
        rename_table_indexes(table_name, new_name)
553 554
      end

555
      def drop_table(table_name, options = {})
556
        execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
557 558
      end

559
      def rename_index(table_name, old_name, new_name)
560
        if supports_rename_index?
561 562
          validate_index_length!(table_name, new_name)

563 564 565 566 567 568
          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 已提交
569
      def change_column_default(table_name, column_name, default) #:nodoc:
570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589
        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)}")
590
        rename_column_indexes(table_name, column_name, new_column_name)
591 592
      end

D
doabit 已提交
593
      def add_index(table_name, column_name, options = {}) #:nodoc:
594 595
        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 已提交
596 597
      end

598
      def foreign_keys(table_name)
599
        fk_info = select_all <<-SQL.strip_heredoc
600 601 602 603 604 605 606 607
          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}'
608
        SQL
609 610 611 612

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

        fk_info.map do |row|
613 614 615 616 617 618
          options = {
            column: row['column'],
            name: row['name'],
            primary_key: row['primary_key']
          }

Y
Yves Senn 已提交
619 620
          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")
621

622 623 624 625
          ForeignKeyDefinition.new(table_name, row['to_table'], options)
        end
      end

626 627
      # Maps logical Rails types to MySQL-specific data types.
      def type_to_sql(type, limit = nil, precision = nil, scale = nil)
628
        case type.to_s
629 630 631 632 633 634 635
        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
636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654
        when 'integer'
          case limit
          when 1; 'tinyint'
          when 2; 'smallint'
          when 3; 'mediumint'
          when nil, 4, 11; 'int(11)'  # compatibility with MySQL default
          when 5..8; 'bigint'
          else raise(ActiveRecordError, "No integer type has byte size #{limit}")
          end
        when 'text'
          case limit
          when 0..0xff;               'tinytext'
          when nil, 0x100..0xffff;    'text'
          when 0x10000..0xffffff;     'mediumtext'
          when 0x1000000..0xffffffff; 'longtext'
          else raise(ActiveRecordError, "No text type has character length #{limit}")
          end
        else
          super
655 656 657 658 659
        end
      end

      # SHOW VARIABLES LIKE 'name'
      def show_variable(name)
K
kennyj 已提交
660
        variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA')
661 662 663 664 665
        variables.first['Value'] unless variables.empty?
      end

      # Returns a table's primary key and belonging sequence.
      def pk_and_sequence_for(table)
K
kennyj 已提交
666 667
        execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
          create_table = each_hash(result).first[:"Create Table"]
668
          if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
S
Seamus Abshere 已提交
669
            keys = $1.split(",").map { |key| key.delete('`"') }
K
kennyj 已提交
670 671 672 673
            keys.length == 1 ? [keys.first, nil] : nil
          else
            nil
          end
674 675 676 677 678 679 680 681 682
        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

683 684
      def case_sensitive_modifier(node, table_attribute)
        node = Arel::Nodes.build_quoted node, table_attribute
685 686 687
        Arel::Nodes::Bin.new(node)
      end

688 689 690 691 692 693 694 695
      def case_sensitive_comparison(table, attribute, column, value)
        if column.case_sensitive?
          table[attribute].eq(value)
        else
          super
        end
      end

696 697 698 699 700 701 702 703
      def case_insensitive_comparison(table, attribute, column, value)
        if column.case_sensitive?
          super
        else
          table[attribute].eq(value)
        end
      end

704
      def strict_mode?
705
        self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
706 707
      end

708 709 710 711
      def valid_type?(type)
        !native_database_types[type].nil?
      end

712 713
      protected

S
Sean Griffin 已提交
714
      def initialize_type_map(m) # :nodoc:
715
        super
716

717 718
        register_class_with_limit m, %r(char)i, MysqlString

719 720 721 722 723 724
        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)
725 726
        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 已提交
727 728 729
        m.register_type %r(^float)i,     Type::Float.new(limit: 24)
        m.register_type %r(^double)i,    Type::Float.new(limit: 53)

730 731 732 733 734 735
        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

736 737 738 739
        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'
740 741 742 743

        m.register_type(%r(enum)i) do |sql_type|
          limit = sql_type[/^enum\((.+)\)/i, 1]
            .split(',').map{|enum| enum.strip.length - 2}.max
744
          MysqlString.new(limit: limit)
745
        end
746 747
      end

748 749 750 751 752 753 754 755 756 757
      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

758
      def extract_precision(sql_type)
759
        if /time/ === sql_type
760 761 762 763 764 765
          super || 0
        else
          super
        end
      end

S
Sean Griffin 已提交
766 767 768 769
      def fetch_type_metadata(sql_type, collation = "", extra = "")
        MysqlTypeMetadata.new(super(sql_type), collation: collation, extra: extra, strict: strict_mode?)
      end

770 771 772 773 774 775 776 777 778 779 780
      # 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

781 782 783 784
      def add_index_length(option_strings, column_names, options = {})
        if options.is_a?(Hash) && length = options[:length]
          case length
          when Hash
785
            column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
786 787 788 789 790 791 792 793
          when Fixnum
            column_names.each {|name| option_strings[name] += "(#{length})"}
          end
        end

        return option_strings
      end

794
      def quoted_columns_for_index(column_names, options = {})
795
        option_strings = Hash[column_names.map {|name| [name, '']}]
796

797 798 799 800 801 802 803
        # 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]}
804 805 806 807 808 809 810 811 812 813 814 815 816 817
      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 = {})
818
        td = create_table_definition(table_name)
819 820
        cd = td.new_column_definition(column_name, type, options)
        schema_creation.visit_AddColumn cd
821 822 823 824 825 826 827 828 829 830 831 832 833
      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

834 835 836
        td = create_table_definition(table_name)
        cd = td.new_column_definition(column.name, type, options)
        schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
837 838 839
      end

      def rename_column_sql(table_name, column_name, new_column_name)
840
        column  = column_for(table_name, column_name)
841 842 843
        options = {
          default: column.default,
          null: column.null,
844
          auto_increment: column.auto_increment?
845
        }
846

K
kennyj 已提交
847
        current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
848 849 850
        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))
851 852
      end

M
Marc-Andre Lafortune 已提交
853 854 855 856 857 858
      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) }
859 860 861 862 863 864 865 866 867 868 869 870
      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

871 872
      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)]
873 874
      end

875
      def remove_timestamps_sql(table_name, options = {})
876 877 878
        [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
      end

879 880
      private

881
      def version
882
        @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map(&:to_i)
883 884 885
      end

      def mariadb?
886
        full_version =~ /mariadb/i
887 888 889 890 891 892
      end

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

893
      def configure_connection
894
        variables = @config.fetch(:variables, {}).stringify_keys
895 896 897

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

        # Increase timeout so the server doesn't disconnect us.
        wait_timeout = @config[:wait_timeout]
        wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
903
        variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
904 905 906 907

        # 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.
908 909
        unless variables.has_key?('sql_mode')
          variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
910 911 912 913 914
        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)
915 916 917 918 919
        if @config[:encoding]
          encoding = "NAMES #{@config[:encoding]}"
          encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
          encoding << ", "
        end
920 921 922 923

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

        # ...and send them all in one query
932
        @connection.query  "SET #{encoding} #{variable_assignments}"
933
      end
934 935 936 937 938 939 940 941 942

      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
943

944
      def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
945 946 947
        TableDefinition.new(native_database_types, name, temporary, options, as)
      end

948
      class MysqlString < Type::String # :nodoc:
949
        def serialize(value)
950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966
          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
967

968 969
      ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql)
      ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
970 971 972
    end
  end
end