abstract_mysql_adapter.rb 34.5 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
      end

16 17 18 19
      class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
        attr_accessor :charset, :collation
      end

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

        def new_column_definition(name, type, options) # :nodoc:
          column = super
          case column.type
          when :primary_key
            column.type = :integer
            column.auto_increment = true
          end
30 31
          column.charset = options[:charset]
          column.collation = options[:collation]
32 33
          column
        end
34 35 36 37 38 39

        private

        def create_column_definition(name, type)
          ColumnDefinition.new(name, type)
        end
40 41
      end

42 43 44 45
      class Table < ActiveRecord::ConnectionAdapters::Table
        include ColumnMethods
      end

46
      class SchemaCreation < AbstractAdapter::SchemaCreation
R
Rafael Mendonça França 已提交
47 48 49 50
        def visit_AddColumn(o)
          add_column_position!(super, column_options(o))
        end

51 52
        private

53 54 55 56
        def visit_DropForeignKey(name)
          "DROP FOREIGN KEY #{name}"
        end

57
        def visit_TableDefinition(o)
R
Rafael Mendonça França 已提交
58 59 60 61 62 63
          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) })

64
          create_sql << "(#{statements.join(', ')}) " if statements.present?
65 66 67 68
          create_sql << "#{o.options}"
          create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
          create_sql
        end
69

70
        def visit_ChangeColumnDefinition(o)
71 72
          change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}"
          add_column_position!(change_column_sql, column_options(o.column))
73 74
        end

75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
        def column_options(o)
          column_options = super
          column_options[:charset] = o.charset
          column_options[:collation] = o.collation
          column_options
        end

        def add_column_options!(sql, options)
          if options[:charset]
            sql << " CHARACTER SET #{options[:charset]}"
          end
          if options[:collation]
            sql << " COLLATE #{options[:collation]}"
          end
          super
        end

92 93
        def add_column_position!(sql, options)
          if options[:first]
94
            sql << " FIRST"
95 96
          elsif options[:after]
            sql << " AFTER #{quote_column_name(options[:after])}"
97 98 99
          end
          sql
        end
100 101

        def index_in_create(table_name, column_name, options)
102
          index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
103
          "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
104
        end
105 106
      end

107 108 109 110
      def update_table_definition(table_name, base) # :nodoc:
        Table.new(table_name, base)
      end

111 112 113 114
      def schema_creation
        SchemaCreation.new self
      end

115 116
      def column_spec_for_primary_key(column)
        spec = {}
117
        if column.auto_increment?
R
Ryuta Kamizono 已提交
118 119
          spec[:id] = ':bigint' if column.bigint?
          return if spec.empty?
120 121 122 123 124 125 126
        else
          spec[:id] = column.type.inspect
          spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) })
        end
        spec
      end

127 128
      def prepare_column_options(column)
        spec = super
129
        spec.delete(:precision) if /time/ === column.sql_type && column.precision == 0
130
        spec.delete(:limit)     if :boolean === column.type
131 132 133 134 135
        if column.collation && table_name = column.instance_variable_get(:@table_name)
          @collation_cache ||= {}
          @collation_cache[table_name] ||= select_one("SHOW TABLE STATUS LIKE '#{table_name}'")["Collation"]
          spec[:collation] = column.collation.inspect if column.collation != @collation_cache[table_name]
        end
136 137 138
        spec
      end

139 140 141 142
      def migration_keys
        super + [:collation]
      end

143
      class Column < ConnectionAdapters::Column # :nodoc:
S
Sean Griffin 已提交
144
        delegate :strict, :collation, :extra, to: :sql_type_metadata, allow_nil: true
145

S
Sean Griffin 已提交
146 147
        def initialize(*)
          super
148
          assert_valid_default(default)
149
          extract_default
150 151
        end

152 153 154
        def extract_default
          if blob_or_text_column?
            @default = null || strict ? nil : ''
S
Sean Griffin 已提交
155
          elsif missing_default_forged_as_empty_string?(default)
156
            @default = nil
157 158 159 160
          end
        end

        def has_default?
161
          return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
162 163
          super
        end
164

165 166 167
        def blob_or_text_column?
          sql_type =~ /blob/i || type == :text
        end
168

169 170 171 172
        def case_sensitive?
          collation && !collation.match(/_ci$/)
        end

173 174 175 176
        def auto_increment?
          extra == 'auto_increment'
        end

177 178 179 180 181 182 183 184 185 186 187 188
        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
189 190 191 192 193 194

        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 已提交
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
      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
219 220

        def attributes_for_hash
S
Sean Griffin 已提交
221
          [self.class, @type_metadata, collation, extra, strict]
222
        end
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
      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 = {
245
        :primary_key => "int(11) auto_increment PRIMARY KEY",
246 247 248 249 250 251 252 253 254 255 256 257
        :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 }
      }

258 259 260
      INDEX_TYPES  = [:fulltext, :spatial]
      INDEX_USINGS = [:btree, :hash]

261 262 263 264 265
      # 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 = {}, {}
266

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

269
        if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
270
          @prepared_statements = true
271
        else
A
Aaron Patterson 已提交
272 273 274 275
          @prepared_statements = false
        end
      end

276 277 278 279 280 281 282 283 284 285
      MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN = 191
      CHARSETS_OF_4BYTES_MAXLEN = ['utf8mb4', 'utf16', 'utf16le', 'utf32']
      def initialize_schema_migrations_table
        if CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
          ActiveRecord::SchemaMigration.create_table(MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN)
        else
          ActiveRecord::SchemaMigration.create_table
        end
      end

286 287 288 289 290 291 292 293 294
      # Returns true, since this connection adapter supports migrations.
      def supports_migrations?
        true
      end

      def supports_primary_key?
        true
      end

295 296 297 298
      def supports_bulk_alter? #:nodoc:
        true
      end

299
      # Technically MySQL allows to create indexes with the sort order syntax
300 301 302 303 304
      # but at the moment (5.5) it doesn't yet implement them
      def supports_index_sort_order?
        true
      end

305 306 307 308 309 310 311 312
      # 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

313 314 315 316
      def supports_indexes_in_create?
        true
      end

317 318 319 320
      def supports_foreign_keys?
        true
      end

321 322 323 324
      def supports_views?
        version[0] >= 5
      end

325 326 327 328
      def supports_datetime_with_precision?
        (version[0] == 5 && version[1] >= 6) || version[0] >= 6
      end

329 330 331 332
      def native_database_types
        NATIVE_DATABASE_TYPES
      end

333 334 335 336
      def index_algorithms
        { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
      end

337 338 339 340 341 342 343 344
      # 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 已提交
345 346
      def new_column(field, default, sql_type_metadata = nil, null = true) # :nodoc:
        Column.new(field, default, sql_type_metadata, null)
347 348
      end

349
      # Must return the MySQL error number from the exception, if the exception has an
350 351 352 353 354 355 356
      # error number.
      def error_number(exception) # :nodoc:
        raise NotImplementedError
      end

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

357 358 359
      def _quote(value) # :nodoc:
        if value.is_a?(Type::Binary::Data)
          "x'#{value.hex}'"
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
        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

377 378 379 380
      def unquoted_true
        1
      end

381 382 383 384
      def quoted_false
        QUOTED_FALSE
      end

385 386 387 388
      def unquoted_false
        0
      end

389 390
      # REFERENTIAL INTEGRITY ====================================

R
Rafael Mendonça França 已提交
391
      def disable_referential_integrity #:nodoc:
392 393 394 395 396 397 398 399 400 401
        old = select_value("SELECT @@FOREIGN_KEY_CHECKS")

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

402
      #--
403
      # DATABASE STATEMENTS ======================================
404
      #++
405

406 407 408 409 410
      def clear_cache!
        super
        reload_type_map
      end

411 412
      # Executes the SQL statement in the context of this connection.
      def execute(sql, name = nil)
413
        log(sql, name) { @connection.query(sql) }
414 415 416
      end

      # MysqlAdapter has to free a result after using it, so we use this method to write
417
      # stuff in an abstract way without concerning ourselves about whether it needs to be
418 419 420 421 422 423 424 425 426 427 428 429 430 431
      # 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

432 433 434 435 436
      def begin_isolated_db_transaction(isolation)
        execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
        begin_db_transaction
      end

437 438 439 440
      def commit_db_transaction #:nodoc:
        execute "COMMIT"
      end

441
      def exec_rollback_db_transaction #:nodoc:
442 443 444 445 446
        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
447
      # these, we must use a subquery.
448 449
      def join_to_update(update, select) #:nodoc:
        if select.limit || select.offset || select.orders.any?
450
          super
451 452 453 454 455 456
        else
          update.table select.source
          update.wheres = select.constraints
        end
      end

J
Jon Leighton 已提交
457 458 459 460
      def empty_insert_statement_value
        "VALUES ()"
      end

461 462 463 464 465 466
      # 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)
467
        sql = create_database(name, options)
468
        reconnect!
469
        sql
470 471 472 473 474 475
      end

      # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
      # Charset defaults to utf8.
      #
      # Example:
A
AvnerCohen 已提交
476
      #   create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
477
      #   create_database 'matt_development'
A
AvnerCohen 已提交
478
      #   create_database 'matt_development', charset: :big5
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508
      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

509 510
      def tables(name = nil, database = nil, like = nil) #:nodoc:
        sql = "SHOW TABLES "
511
        sql << "IN #{quote_table_name(database)} " if database
512
        sql << "LIKE #{quote(like)}" if like
513 514

        execute_and_free(sql, 'SCHEMA') do |result|
515
          result.collect(&:first)
516 517 518
        end
      end

519 520 521 522
      def truncate(table_name, name = nil)
        execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
      end

523
      def table_exists?(name)
524
        return false unless name.present?
525
        return true if tables(nil, nil, name).any?
526 527 528 529 530 531 532 533 534

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

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

535
        tables(nil, schema, table).any?
536 537 538 539 540 541 542 543 544 545 546
      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]
547 548 549 550 551

              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)
552 553 554 555 556 557 558 559 560 561 562
            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+.
563
      def columns(table_name)#:nodoc:
564
        sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
565 566
        execute_and_free(sql, 'SCHEMA') do |result|
          each_hash(result).map do |field|
567
            field_name = set_field_encoding(field[:Field])
568
            sql_type = field[:Type]
S
Sean Griffin 已提交
569 570
            type_metadata = fetch_type_metadata(sql_type, field[:Collation], field[:Extra])
            new_column(field_name, field[:Default], type_metadata, field[:Null] == "YES")
571 572 573 574 575 576 577 578
          end
        end
      end

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

579
      def bulk_change_table(table_name, operations) #:nodoc:
580
        sqls = operations.flat_map do |command, args|
581 582 583
          table, arguments = args.shift, args
          method = :"#{command}_sql"

584
          if respond_to?(method, true)
585 586 587 588
            send(method, table, *arguments)
          else
            raise "Unknown method called : #{method}(#{arguments.inspect})"
          end
589
        end.join(", ")
590 591 592 593

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

594 595 596 597 598 599
      # 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)}"
600
        rename_table_indexes(table_name, new_name)
601 602
      end

603 604 605 606 607 608
      # Drops a table from the database.
      #
      # [<tt>:force</tt>]
      #   Set to +:cascade+ to drop dependent objects as well.
      #   Defaults to false.
      # [<tt>:if_exists</tt>]
609
      #   Set to +true+ to only drop the table if it exists.
610 611 612 613
      #   Defaults to false.
      # [<tt>:temporary</tt>]
      #   Set to +true+ to drop temporary table.
      #   Defaults to false.
614 615 616 617
      #
      # Although this command ignores most +options+ and the block if one is given,
      # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
      # In that case, +options+ and the block will be used by create_table.
618
      def drop_table(table_name, options = {})
619
        execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
620 621
      end

622
      def rename_index(table_name, old_name, new_name)
623
        if supports_rename_index?
624 625
          validate_index_length!(table_name, new_name)

626 627 628 629 630 631
          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 已提交
632
      def change_column_default(table_name, column_name, default) #:nodoc:
633 634 635 636
        column = column_for(table_name, column_name)
        change_column table_name, column_name, column.sql_type, :default => default
      end

637
      def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
638 639 640 641 642 643 644 645 646 647 648 649 650 651 652
        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)}")
653
        rename_column_indexes(table_name, column_name, new_column_name)
654 655
      end

D
doabit 已提交
656
      def add_index(table_name, column_name, options = {}) #:nodoc:
657 658
        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 已提交
659 660
      end

661
      def foreign_keys(table_name)
662
        fk_info = select_all <<-SQL.strip_heredoc
663 664 665 666 667 668 669 670
          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}'
671
        SQL
672 673 674 675

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

        fk_info.map do |row|
676 677 678 679 680 681
          options = {
            column: row['column'],
            name: row['name'],
            primary_key: row['primary_key']
          }

Y
Yves Senn 已提交
682 683
          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")
684

685 686 687 688
          ForeignKeyDefinition.new(table_name, row['to_table'], options)
        end
      end

689 690
      # Maps logical Rails types to MySQL-specific data types.
      def type_to_sql(type, limit = nil, precision = nil, scale = nil)
691
        case type.to_s
692 693 694 695 696 697 698
        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
699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717
        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
718 719 720 721 722
        end
      end

      # SHOW VARIABLES LIKE 'name'
      def show_variable(name)
K
kennyj 已提交
723
        variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA')
724 725 726 727 728
        variables.first['Value'] unless variables.empty?
      end

      # Returns a table's primary key and belonging sequence.
      def pk_and_sequence_for(table)
K
kennyj 已提交
729 730
        execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
          create_table = each_hash(result).first[:"Create Table"]
731
          if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
S
Seamus Abshere 已提交
732
            keys = $1.split(",").map { |key| key.delete('`"') }
K
kennyj 已提交
733 734 735 736
            keys.length == 1 ? [keys.first, nil] : nil
          else
            nil
          end
737 738 739 740 741 742 743 744 745
        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

746 747
      def case_sensitive_modifier(node, table_attribute)
        node = Arel::Nodes.build_quoted node, table_attribute
748 749 750
        Arel::Nodes::Bin.new(node)
      end

751 752 753 754 755 756 757 758
      def case_sensitive_comparison(table, attribute, column, value)
        if column.case_sensitive?
          table[attribute].eq(value)
        else
          super
        end
      end

759 760 761 762 763 764 765 766
      def case_insensitive_comparison(table, attribute, column, value)
        if column.case_sensitive?
          super
        else
          table[attribute].eq(value)
        end
      end

767
      def strict_mode?
768
        self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
769 770
      end

771 772 773 774
      def valid_type?(type)
        !native_database_types[type].nil?
      end

775 776
      protected

S
Sean Griffin 已提交
777
      def initialize_type_map(m) # :nodoc:
778
        super
779

780 781
        register_class_with_limit m, %r(char)i, MysqlString

782 783 784 785 786 787
        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)
788 789
        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 已提交
790 791 792
        m.register_type %r(^float)i,     Type::Float.new(limit: 24)
        m.register_type %r(^double)i,    Type::Float.new(limit: 53)

793 794 795 796 797 798
        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

799 800 801 802
        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'
803 804 805 806

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

811 812 813 814 815 816 817 818 819 820
      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

821
      def extract_precision(sql_type)
822
        if /time/ === sql_type
823 824 825 826 827 828
          super || 0
        else
          super
        end
      end

S
Sean Griffin 已提交
829 830 831 832
      def fetch_type_metadata(sql_type, collation = "", extra = "")
        MysqlTypeMetadata.new(super(sql_type), collation: collation, extra: extra, strict: strict_mode?)
      end

833 834 835 836
      def add_index_length(option_strings, column_names, options = {})
        if options.is_a?(Hash) && length = options[:length]
          case length
          when Hash
837
            column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
838 839 840 841 842 843 844 845
          when Fixnum
            column_names.each {|name| option_strings[name] += "(#{length})"}
          end
        end

        return option_strings
      end

846
      def quoted_columns_for_index(column_names, options = {})
847
        option_strings = Hash[column_names.map {|name| [name, '']}]
848

849 850 851 852 853 854 855
        # 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]}
856 857 858 859 860 861 862 863 864 865 866 867 868 869
      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 = {})
870
        td = create_table_definition(table_name)
871 872
        cd = td.new_column_definition(column_name, type, options)
        schema_creation.visit_AddColumn cd
873 874 875 876 877 878 879 880 881 882 883 884 885
      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

886 887 888
        td = create_table_definition(table_name)
        cd = td.new_column_definition(column.name, type, options)
        schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
889 890 891
      end

      def rename_column_sql(table_name, column_name, new_column_name)
892
        column  = column_for(table_name, column_name)
893 894 895
        options = {
          default: column.default,
          null: column.null,
896
          auto_increment: column.auto_increment?
897
        }
898

K
kennyj 已提交
899
        current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
900 901 902
        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))
903 904
      end

M
Marc-Andre Lafortune 已提交
905 906 907 908 909 910
      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) }
911 912 913 914 915 916 917 918 919 920 921 922
      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

923 924
      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)]
925 926
      end

927
      def remove_timestamps_sql(table_name, options = {})
928 929 930
        [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
      end

931 932
      private

933 934 935 936 937 938 939 940 941 942 943 944 945
      # 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)
        # Materialized subquery by adding distinct
        # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
        subselect.from subsubselect.distinct.as('__active_record_temp')
      end

946
      def version
947
        @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map(&:to_i)
948 949 950
      end

      def mariadb?
951
        full_version =~ /mariadb/i
952 953 954 955 956 957
      end

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

958
      def configure_connection
959
        variables = @config.fetch(:variables, {}).stringify_keys
960

961
        # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
962
        variables['sql_auto_is_null'] = 0
963 964 965 966

        # Increase timeout so the server doesn't disconnect us.
        wait_timeout = @config[:wait_timeout]
        wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
967
        variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
968 969

        # Make MySQL reject illegal values rather than truncating or blanking them, see
970
        # http://dev.mysql.com/doc/refman/5.6/en/sql-mode.html#sqlmode_strict_all_tables
971
        # If the user has provided another value for sql_mode, don't replace it.
972 973
        unless variables.has_key?('sql_mode')
          variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
974 975 976
        end

        # NAMES does not have an equals sign, see
977
        # http://dev.mysql.com/doc/refman/5.6/en/set-statement.html#id944430
978
        # (trailing comma because variable_assignments will always have content)
979 980 981 982 983
        if @config[:encoding]
          encoding = "NAMES #{@config[:encoding]}"
          encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
          encoding << ", "
        end
984 985 986 987

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

        # ...and send them all in one query
996
        @connection.query  "SET #{encoding} #{variable_assignments}"
997
      end
998 999 1000 1001 1002 1003 1004 1005 1006

      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
1007

1008
      def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
1009 1010 1011
        TableDefinition.new(native_database_types, name, temporary, options, as)
      end

1012
      class MysqlString < Type::String # :nodoc:
1013
        def serialize(value)
1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030
          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
1031

1032 1033
      ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql)
      ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
1034 1035 1036
    end
  end
end