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

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

9 10 11
      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
      class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
17
        attr_accessor :charset
18 19
      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
          column.charset = options[:charset]
31 32
          column
        end
33 34 35 36 37 38

        private

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

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

45
      class SchemaCreation < AbstractAdapter::SchemaCreation
46 47
        private

48 49 50 51
        def visit_DropForeignKey(name)
          "DROP FOREIGN KEY #{name}"
        end

52
        def visit_TableDefinition(o)
R
Rafael Mendonça França 已提交
53 54 55 56 57 58
          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) })

59
          create_sql << "(#{statements.join(', ')}) " if statements.present?
60 61 62 63
          create_sql << "#{o.options}"
          create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
          create_sql
        end
64

65 66 67 68
        def visit_AddColumnDefinition(o)
          add_column_position!(super, column_options(o.column))
        end

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

74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
        def column_options(o)
          column_options = super
          column_options[:charset] = o.charset
          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

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

        def index_in_create(table_name, column_name, options)
100 101
          index_name, index_type, index_columns, _, _, index_using = @conn.add_index_options(table_name, column_name, options)
          "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns}) "
102
        end
103 104
      end

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

109 110 111 112
      def schema_creation
        SchemaCreation.new self
      end

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

125 126
      def prepare_column_options(column)
        spec = super
127
        spec.delete(:precision) if /time/ === column.sql_type && column.precision == 0
128
        spec.delete(:limit)     if :boolean === column.type
129 130 131 132
        spec
      end

      def schema_collation(column)
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"]
136
          column.collation.inspect if column.collation != @collation_cache[table_name]
137 138
        end
      end
139
      private :schema_collation
140

141
      class Column < ConnectionAdapters::Column # :nodoc:
142
        delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true
143

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

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

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

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

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

171 172 173 174
        def auto_increment?
          extra == 'auto_increment'
        end

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

        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 已提交
193 194 195
      end

      class MysqlTypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
196
        attr_reader :extra, :strict
S
Sean Griffin 已提交
197

198
        def initialize(type_metadata, extra: "", strict: false)
S
Sean Griffin 已提交
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
          super(type_metadata)
          @type_metadata = type_metadata
          @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
216 217

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

255 256 257
      INDEX_TYPES  = [:fulltext, :spatial]
      INDEX_USINGS = [:btree, :hash]

258 259 260 261 262
      # 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 = {}, {}
263

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

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

273 274 275 276 277 278 279 280 281 282
      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

283 284 285 286 287 288 289 290 291
      # Returns true, since this connection adapter supports migrations.
      def supports_migrations?
        true
      end

      def supports_primary_key?
        true
      end

292 293 294 295
      def supports_bulk_alter? #:nodoc:
        true
      end

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

302 303 304 305 306 307 308 309
      # 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

310 311 312 313
      def supports_indexes_in_create?
        true
      end

314 315 316 317
      def supports_foreign_keys?
        true
      end

318 319 320 321
      def supports_views?
        version[0] >= 5
      end

322 323 324 325
      def supports_datetime_with_precision?
        (version[0] == 5 && version[1] >= 6) || version[0] >= 6
      end

326 327 328 329
      def native_database_types
        NATIVE_DATABASE_TYPES
      end

330 331 332 333
      def index_algorithms
        { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
      end

334 335 336 337 338 339 340 341
      # 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

342 343
      def new_column(field, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc:
        Column.new(field, default, sql_type_metadata, null, default_function, collation)
344 345
      end

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

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

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

374 375 376 377
      def unquoted_true
        1
      end

378 379 380 381
      def quoted_false
        QUOTED_FALSE
      end

382 383 384 385
      def unquoted_false
        0
      end

386 387
      # REFERENTIAL INTEGRITY ====================================

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

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

399
      #--
400
      # DATABASE STATEMENTS ======================================
401
      #++
402

403 404 405 406 407
      def clear_cache!
        super
        reload_type_map
      end

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

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

429 430 431 432 433
      def begin_isolated_db_transaction(isolation)
        execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
        begin_db_transaction
      end

434 435 436 437
      def commit_db_transaction #:nodoc:
        execute "COMMIT"
      end

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

J
Jon Leighton 已提交
454 455 456 457
      def empty_insert_statement_value
        "VALUES ()"
      end

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

      # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
      # Charset defaults to utf8.
      #
      # Example:
A
AvnerCohen 已提交
473
      #   create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
474
      #   create_database 'matt_development'
A
AvnerCohen 已提交
475
      #   create_database 'matt_development', charset: :big5
476 477 478 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
      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

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

        execute_and_free(sql, 'SCHEMA') do |result|
512
          result.collect(&:first)
513 514 515
        end
      end

516 517 518 519
      def truncate(table_name, name = nil)
        execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
      end

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

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

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

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

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

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

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

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

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

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

600 601 602 603 604 605
      # 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>]
606
      #   Set to +true+ to only drop the table if it exists.
607 608 609 610
      #   Defaults to false.
      # [<tt>:temporary</tt>]
      #   Set to +true+ to drop temporary table.
      #   Defaults to false.
611 612 613 614
      #
      # 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.
615
      def drop_table(table_name, options = {})
616
        execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
617 618
      end

619
      def rename_index(table_name, old_name, new_name)
620
        if supports_rename_index?
621 622
          validate_index_length!(table_name, new_name)

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

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

D
doabit 已提交
653
      def add_index(table_name, column_name, options = {}) #:nodoc:
654 655
        index_name, index_type, index_columns, _, 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_algorithm}"
D
doabit 已提交
656 657
      end

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

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

        fk_info.map do |row|
673 674 675 676 677 678
          options = {
            column: row['column'],
            name: row['name'],
            primary_key: row['primary_key']
          }

Y
Yves Senn 已提交
679 680
          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")
681

682 683 684 685
          ForeignKeyDefinition.new(table_name, row['to_table'], options)
        end
      end

686 687 688 689 690 691 692 693 694 695
      def table_options(table_name)
        create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]

        # strip create_definitions and partition_options
        raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip

        # strip AUTO_INCREMENT
        raw_table_options.sub(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
      end

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

      # SHOW VARIABLES LIKE 'name'
      def show_variable(name)
K
kennyj 已提交
730
        variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA')
731 732 733 734 735
        variables.first['Value'] unless variables.empty?
      end

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

753 754
      def case_sensitive_modifier(node, table_attribute)
        node = Arel::Nodes.build_quoted node, table_attribute
755 756 757
        Arel::Nodes::Bin.new(node)
      end

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

766 767 768 769 770 771 772 773
      def case_insensitive_comparison(table, attribute, column, value)
        if column.case_sensitive?
          super
        else
          table[attribute].eq(value)
        end
      end

774
      def strict_mode?
775
        self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
776 777
      end

778 779 780 781
      def valid_type?(type)
        !native_database_types[type].nil?
      end

782 783
      protected

S
Sean Griffin 已提交
784
      def initialize_type_map(m) # :nodoc:
785
        super
786

787 788
        register_class_with_limit m, %r(char)i, MysqlString

789 790 791 792 793 794
        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)
795 796
        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 已提交
797 798 799
        m.register_type %r(^float)i,     Type::Float.new(limit: 24)
        m.register_type %r(^double)i,    Type::Float.new(limit: 53)

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

806 807 808 809
        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'
810 811 812 813

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

818 819 820 821 822 823 824 825 826 827
      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

828
      def extract_precision(sql_type)
829
        if /time/ === sql_type
830 831 832 833 834 835
          super || 0
        else
          super
        end
      end

836 837
      def fetch_type_metadata(sql_type, extra = "")
        MysqlTypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
S
Sean Griffin 已提交
838 839
      end

840 841 842 843 844 845 846 847
      # 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)
848 849 850
        # 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')
851 852
      end

853 854 855 856
      def add_index_length(option_strings, column_names, options = {})
        if options.is_a?(Hash) && length = options[:length]
          case length
          when Hash
857
            column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
858 859 860 861 862 863 864 865
          when Fixnum
            column_names.each {|name| option_strings[name] += "(#{length})"}
          end
        end

        return option_strings
      end

866
      def quoted_columns_for_index(column_names, options = {})
867
        option_strings = Hash[column_names.map {|name| [name, '']}]
868

869 870 871 872 873 874 875
        # 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]}
876 877 878 879 880 881 882 883 884 885 886 887 888 889
      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 = {})
890
        td = create_table_definition(table_name)
891
        cd = td.new_column_definition(column_name, type, options)
892
        schema_creation.accept(AddColumnDefinition.new(cd))
893 894 895 896 897 898 899 900 901 902 903 904 905
      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

906 907 908
        td = create_table_definition(table_name)
        cd = td.new_column_definition(column.name, type, options)
        schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
909 910 911
      end

      def rename_column_sql(table_name, column_name, new_column_name)
912
        column  = column_for(table_name, column_name)
913 914 915
        options = {
          default: column.default,
          null: column.null,
916
          auto_increment: column.auto_increment?
917
        }
918

K
kennyj 已提交
919
        current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
920 921 922
        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))
923 924
      end

M
Marc-Andre Lafortune 已提交
925 926 927 928 929 930
      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) }
931 932 933 934 935 936 937 938 939 940 941 942
      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

943 944
      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)]
945 946
      end

947
      def remove_timestamps_sql(table_name, options = {})
948 949 950
        [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
      end

951 952
      private

953
      def version
954
        @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map(&:to_i)
955 956 957
      end

      def mariadb?
958
        full_version =~ /mariadb/i
959 960 961 962 963 964
      end

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

965
      def configure_connection
966
        variables = @config.fetch(:variables, {}).stringify_keys
967

968
        # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
969
        variables['sql_auto_is_null'] = 0
970 971 972 973

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

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

        # NAMES does not have an equals sign, see
984
        # http://dev.mysql.com/doc/refman/5.6/en/set-statement.html#id944430
985
        # (trailing comma because variable_assignments will always have content)
986 987 988 989 990
        if @config[:encoding]
          encoding = "NAMES #{@config[:encoding]}"
          encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
          encoding << ", "
        end
991 992 993 994

        # Gather up all of the SET variables...
        variable_assignments = variables.map do |k, v|
          if v == ':default' || v == :default
995
            "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
996
          elsif !v.nil?
997
            "@@SESSION.#{k} = #{quote(v)}"
998 999 1000 1001 1002
          end
          # or else nil; compact to clear nils out
        end.compact.join(', ')

        # ...and send them all in one query
1003
        @connection.query  "SET #{encoding} #{variable_assignments}"
1004
      end
1005 1006 1007 1008 1009 1010 1011 1012 1013

      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
1014

1015
      def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
1016 1017 1018
        TableDefinition.new(native_database_types, name, temporary, options, as)
      end

1019
      class MysqlString < Type::String # :nodoc:
1020
        def serialize(value)
1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037
          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
1038

1039 1040
      ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql)
      ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
1041 1042 1043
    end
  end
end