abstract_mysql_adapter.rb 36.4 KB
Newer Older
1
require 'active_record/connection_adapters/abstract_adapter'
2
require 'active_record/connection_adapters/mysql/schema_creation'
3
require 'active_record/connection_adapters/mysql/schema_definitions'
4
require 'active_record/connection_adapters/mysql/schema_dumper'
5

6
require 'active_support/core_ext/string/strip'
7 8 9 10

module ActiveRecord
  module ConnectionAdapters
    class AbstractMysqlAdapter < AbstractAdapter
11
      include MySQL::ColumnDumper
12 13
      include Savepoints

14
      def update_table_definition(table_name, base) # :nodoc:
15
        MySQL::Table.new(table_name, base)
16 17
      end

18
      def schema_creation
19
        MySQL::SchemaCreation.new(self)
20 21
      end

22
      class Column < ConnectionAdapters::Column # :nodoc:
23
        delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true
24

S
Sean Griffin 已提交
25 26
        def initialize(*)
          super
27
          assert_valid_default(default)
28
          extract_default
29 30
        end

31 32 33
        def extract_default
          if blob_or_text_column?
            @default = null || strict ? nil : ''
S
Sean Griffin 已提交
34
          elsif missing_default_forged_as_empty_string?(default)
35
            @default = nil
36 37 38 39
          end
        end

        def has_default?
40
          return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
41 42
          super
        end
43

44 45 46
        def blob_or_text_column?
          sql_type =~ /blob/i || type == :text
        end
47

48 49 50 51
        def unsigned?
          /unsigned/ === sql_type
        end

52 53 54 55
        def case_sensitive?
          collation && !collation.match(/_ci$/)
        end

56 57 58 59
        def auto_increment?
          extra == 'auto_increment'
        end

60 61 62 63 64 65 66 67 68 69 70 71
        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
72 73 74 75 76 77

        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 已提交
78 79 80
      end

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

83
        def initialize(type_metadata, extra: "", strict: false)
S
Sean Griffin 已提交
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
          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
101 102

        def attributes_for_hash
103
          [self.class, @type_metadata, extra, strict]
104
        end
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
      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 = {
127
        primary_key: "int auto_increment PRIMARY KEY",
128 129 130 131 132 133 134 135 136
        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" },
137
        blob:        { name: "blob" },
138 139 140
        boolean:     { name: "tinyint", limit: 1 },
        bigint:      { name: "bigint" },
        json:        { name: "json" },
141 142
      }

143 144 145
      INDEX_TYPES  = [:fulltext, :spatial]
      INDEX_USINGS = [:btree, :hash]

146 147 148 149 150
      # 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 = {}, {}
151

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

154
        if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
155
          @prepared_statements = true
156
          @visitor.extend(DetermineIfPreparableVisitor)
157
        else
A
Aaron Patterson 已提交
158 159 160 161
          @prepared_statements = false
        end
      end

162 163 164 165 166 167 168 169 170 171
      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

172 173 174 175
      def version
        @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
      end

176 177 178 179 180 181 182 183 184
      # Returns true, since this connection adapter supports migrations.
      def supports_migrations?
        true
      end

      def supports_primary_key?
        true
      end

185 186 187 188
      def supports_bulk_alter? #:nodoc:
        true
      end

189
      # Technically MySQL allows to create indexes with the sort order syntax
190 191 192 193 194
      # but at the moment (5.5) it doesn't yet implement them
      def supports_index_sort_order?
        true
      end

195 196 197 198 199
      # 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?
200
        version >= '5.0.0'
201 202
      end

203 204 205 206
      def supports_explain?
        true
      end

207 208 209 210
      def supports_indexes_in_create?
        true
      end

211 212 213 214
      def supports_foreign_keys?
        true
      end

215
      def supports_views?
216
        version >= '5.0.0'
217 218
      end

219
      def supports_datetime_with_precision?
220
        version >= '5.6.4'
221 222
      end

223 224 225 226
      def native_database_types
        NATIVE_DATABASE_TYPES
      end

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

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

239 240
      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)
241 242
      end

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

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

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

271 272 273 274
      def unquoted_true
        1
      end

275 276 277 278
      def quoted_false
        QUOTED_FALSE
      end

279 280 281 282
      def unquoted_false
        0
      end

283 284 285 286 287 288 289 290
      def quoted_date(value)
        if supports_datetime_with_precision?
          super
        else
          super.sub(/\.\d{6}\z/, '')
        end
      end

291 292
      # REFERENTIAL INTEGRITY ====================================

R
Rafael Mendonça França 已提交
293
      def disable_referential_integrity #:nodoc:
294 295 296 297 298 299 300 301 302 303
        old = select_value("SELECT @@FOREIGN_KEY_CHECKS")

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

304
      #--
305
      # DATABASE STATEMENTS ======================================
306
      #++
307

308 309 310 311 312 313 314 315 316 317
      def explain(arel, binds = [])
        sql     = "EXPLAIN #{to_sql(arel, binds)}"
        start   = Time.now
        result  = exec_query(sql, 'EXPLAIN', binds)
        elapsed = Time.now - start

        ExplainPrettyPrinter.new.pp(result, elapsed)
      end

      class ExplainPrettyPrinter # :nodoc:
318
        # Pretty prints the result of an EXPLAIN in a way that resembles the output of the
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
        # MySQL shell:
        #
        #   +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
        #   | id | select_type | table | type  | possible_keys | key     | key_len | ref   | rows | Extra       |
        #   +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
        #   |  1 | SIMPLE      | users | const | PRIMARY       | PRIMARY | 4       | const |    1 |             |
        #   |  1 | SIMPLE      | posts | ALL   | NULL          | NULL    | NULL    | NULL  |    1 | Using where |
        #   +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
        #   2 rows in set (0.00 sec)
        #
        # This is an exercise in Ruby hyperrealism :).
        def pp(result, elapsed)
          widths    = compute_column_widths(result)
          separator = build_separator(widths)

          pp = []

          pp << separator
          pp << build_cells(result.columns, widths)
          pp << separator

          result.rows.each do |row|
            pp << build_cells(row, widths)
          end

          pp << separator
          pp << build_footer(result.rows.length, elapsed)

          pp.join("\n") + "\n"
        end

        private

        def compute_column_widths(result)
          [].tap do |widths|
            result.columns.each_with_index do |column, i|
              cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
              widths << cells_in_column.map(&:length).max
            end
          end
        end

        def build_separator(widths)
          padding = 1
          '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
        end

        def build_cells(items, widths)
          cells = []
          items.each_with_index do |item, i|
            item = 'NULL' if item.nil?
            justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
            cells << item.to_s.send(justifier, widths[i])
          end
          '| ' + cells.join(' | ') + ' |'
        end

        def build_footer(nrows, elapsed)
          rows_label = nrows == 1 ? 'row' : 'rows'
          "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
        end
      end

382 383 384 385 386
      def clear_cache!
        super
        reload_type_map
      end

387 388
      # Executes the SQL statement in the context of this connection.
      def execute(sql, name = nil)
389
        log(sql, name) { @connection.query(sql) }
390 391 392
      end

      # MysqlAdapter has to free a result after using it, so we use this method to write
393
      # stuff in an abstract way without concerning ourselves about whether it needs to be
394 395 396 397 398 399 400 401 402 403 404 405 406 407
      # 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

408 409 410 411 412
      def begin_isolated_db_transaction(isolation)
        execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
        begin_db_transaction
      end

413 414 415 416
      def commit_db_transaction #:nodoc:
        execute "COMMIT"
      end

417
      def exec_rollback_db_transaction #:nodoc:
418 419 420 421 422
        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
423
      # these, we must use a subquery.
424 425
      def join_to_update(update, select) #:nodoc:
        if select.limit || select.offset || select.orders.any?
426
          super
427 428 429 430 431 432
        else
          update.table select.source
          update.wheres = select.constraints
        end
      end

J
Jon Leighton 已提交
433 434 435 436
      def empty_insert_statement_value
        "VALUES ()"
      end

437 438 439 440 441 442
      # 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)
443
        sql = create_database(name, options)
444
        reconnect!
445
        sql
446 447 448 449 450 451
      end

      # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
      # Charset defaults to utf8.
      #
      # Example:
A
AvnerCohen 已提交
452
      #   create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
453
      #   create_database 'matt_development'
A
AvnerCohen 已提交
454
      #   create_database 'matt_development', charset: :big5
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484
      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

R
Ryuta Kamizono 已提交
485
      def tables(name = nil) # :nodoc:
M
Matt Jones 已提交
486
        sql = "SELECT table_name FROM information_schema.tables "
R
Ryuta Kamizono 已提交
487
        sql << "WHERE table_schema = #{quote(@config[:database])}"
488

R
Ryuta Kamizono 已提交
489
        select_values(sql, 'SCHEMA')
490
      end
491
      alias data_sources tables
492

493 494 495 496
      def truncate(table_name, name = nil)
        execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
      end

497 498
      def table_exists?(table_name)
        return false unless table_name.present?
499

500 501
        schema, name = table_name.to_s.split('.', 2)
        schema, name = @config[:database], schema unless name # A table was provided without a schema
502

503 504
        sql = "SELECT table_name FROM information_schema.tables "
        sql << "WHERE table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
505

506
        select_values(sql, 'SCHEMA').any?
507
      end
508
      alias data_source_exists? table_exists?
509

510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
      def views # :nodoc:
        select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", 'SCHEMA')
      end

      def view_exists?(view_name) # :nodoc:
        return false unless view_name.present?

        schema, name = view_name.to_s.split('.', 2)
        schema, name = @config[:database], schema unless name # A view was provided without a schema

        sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'VIEW'"
        sql << " AND table_schema = #{quote(schema)} AND table_name = #{quote(name)}"

        select_values(sql, 'SCHEMA').any?
      end

526 527 528 529 530 531 532 533 534
      # 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]
535 536 537 538 539

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

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

567
      def bulk_change_table(table_name, operations) #:nodoc:
568
        sqls = operations.flat_map do |command, args|
569 570 571
          table, arguments = args.shift, args
          method = :"#{command}_sql"

572
          if respond_to?(method, true)
573 574 575 576
            send(method, table, *arguments)
          else
            raise "Unknown method called : #{method}(#{arguments.inspect})"
          end
577
        end.join(", ")
578 579 580 581

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

582 583 584 585 586 587
      # 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)}"
588
        rename_table_indexes(table_name, new_name)
589 590
      end

591 592 593 594 595 596
      # 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>]
597
      #   Set to +true+ to only drop the table if it exists.
598 599 600 601
      #   Defaults to false.
      # [<tt>:temporary</tt>]
      #   Set to +true+ to drop temporary table.
      #   Defaults to false.
602 603 604 605
      #
      # 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.
606
      def drop_table(table_name, options = {})
607
        execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
608 609
      end

610
      def rename_index(table_name, old_name, new_name)
611
        if supports_rename_index?
612 613
          validate_index_length!(table_name, new_name)

614 615 616 617 618 619
          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

620 621
      def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
        default = extract_new_default_value(default_or_changes)
622 623 624 625
        column = column_for(table_name, column_name)
        change_column table_name, column_name, column.sql_type, :default => default
      end

626
      def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
627 628 629 630 631 632 633 634 635 636 637 638 639 640 641
        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)}")
642
        rename_column_indexes(table_name, column_name, new_column_name)
643 644
      end

D
doabit 已提交
645
      def add_index(table_name, column_name, options = {}) #:nodoc:
646 647
        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 已提交
648 649
      end

650
      def foreign_keys(table_name)
651
        fk_info = select_all <<-SQL.strip_heredoc
652 653 654 655 656 657 658 659
          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}'
660
        SQL
661

662
        create_table_info = create_table_info(table_name)
663 664

        fk_info.map do |row|
665 666 667 668 669 670
          options = {
            column: row['column'],
            name: row['name'],
            primary_key: row['primary_key']
          }

Y
Yves Senn 已提交
671 672
          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")
673

674 675 676 677
          ForeignKeyDefinition.new(table_name, row['to_table'], options)
        end
      end

678
      def table_options(table_name)
679
        create_table_info = create_table_info(table_name)
680 681 682 683 684 685 686 687

        # 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

688
      # Maps logical Rails types to MySQL-specific data types.
689 690
      def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil)
        sql = case type.to_s
691
        when 'integer'
692
          integer_to_sql(limit)
693
        when 'text'
694
          text_to_sql(limit)
695 696 697 698 699 700 701 702
        when 'blob'
          binary_to_sql(limit)
        when 'binary'
          if (0..0xfff) === limit
            "varbinary(#{limit})"
          else
            binary_to_sql(limit)
          end
703
        else
704
          super(type, limit, precision, scale)
705
        end
706 707 708

        sql << ' unsigned' if unsigned && type != :primary_key
        sql
709 710 711 712
      end

      # SHOW VARIABLES LIKE 'name'
      def show_variable(name)
713 714 715 716
        variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
        variables.first['Value'] unless variables.empty?
      rescue ActiveRecord::StatementInvalid
        nil
717 718
      end

719 720 721 722 723 724 725 726 727 728 729 730 731 732
      def primary_keys(table_name) # :nodoc:
        raise ArgumentError unless table_name.present?

        schema, name = table_name.to_s.split('.', 2)
        schema, name = @config[:database], schema unless name # A table was provided without a schema

        select_values(<<-SQL.strip_heredoc, 'SCHEMA')
          SELECT column_name
          FROM information_schema.key_column_usage
          WHERE constraint_name = 'PRIMARY'
            AND table_schema = #{quote(schema)}
            AND table_name = #{quote(name)}
          ORDER BY ordinal_position
        SQL
733 734
      end

735 736
      def case_sensitive_modifier(node, table_attribute)
        node = Arel::Nodes.build_quoted node, table_attribute
737 738 739
        Arel::Nodes::Bin.new(node)
      end

740 741 742 743 744 745 746 747
      def case_sensitive_comparison(table, attribute, column, value)
        if column.case_sensitive?
          table[attribute].eq(value)
        else
          super
        end
      end

748 749 750 751 752 753 754 755
      def case_insensitive_comparison(table, attribute, column, value)
        if column.case_sensitive?
          super
        else
          table[attribute].eq(value)
        end
      end

756
      def strict_mode?
757
        self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
758 759
      end

760 761 762 763
      def valid_type?(type)
        !native_database_types[type].nil?
      end

764 765
      protected

S
Sean Griffin 已提交
766
      def initialize_type_map(m) # :nodoc:
767
        super
768

769 770
        register_class_with_limit m, %r(char)i, MysqlString

771 772 773 774 775 776
        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)
777 778
        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 已提交
779 780
        m.register_type %r(^float)i,     Type::Float.new(limit: 24)
        m.register_type %r(^double)i,    Type::Float.new(limit: 53)
781
        m.register_type %r(^json)i,      MysqlJson.new
S
Sean Griffin 已提交
782

783 784 785 786 787 788
        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

789 790 791
        m.alias_type %r(tinyint\(1\))i,  'boolean' if emulate_booleans
        m.alias_type %r(year)i,          'integer'
        m.alias_type %r(bit)i,           'binary'
792 793 794 795

        m.register_type(%r(enum)i) do |sql_type|
          limit = sql_type[/^enum\((.+)\)/i, 1]
            .split(',').map{|enum| enum.strip.length - 2}.max
796
          MysqlString.new(limit: limit)
797
        end
798 799 800 801 802 803

        m.register_type(%r(^set)i) do |sql_type|
          limit = sql_type[/^set\((.+)\)/i, 1]
            .split(',').map{|set| set.strip.length - 1}.sum - 1
          MysqlString.new(limit: limit)
        end
804 805
      end

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

816
      def extract_precision(sql_type)
817
        if /time/ === sql_type
818 819 820 821 822 823
          super || 0
        else
          super
        end
      end

824 825
      def fetch_type_metadata(sql_type, extra = "")
        MysqlTypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
S
Sean Griffin 已提交
826 827
      end

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

        return option_strings
      end

841
      def quoted_columns_for_index(column_names, options = {})
842
        option_strings = Hash[column_names.map {|name| [name, '']}]
843

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

881 882 883
        td = create_table_definition(table_name)
        cd = td.new_column_definition(column.name, type, options)
        schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
884 885 886
      end

      def rename_column_sql(table_name, column_name, new_column_name)
887
        column  = column_for(table_name, column_name)
888 889 890
        options = {
          default: column.default,
          null: column.null,
891
          auto_increment: column.auto_increment?
892
        }
893

K
kennyj 已提交
894
        current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
895 896 897
        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))
898 899
      end

M
Marc-Andre Lafortune 已提交
900 901 902 903 904 905
      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) }
906 907 908
      end

      def add_index_sql(table_name, column_name, options = {})
909 910 911
        index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
        index_algorithm[0, 0] = ", " if index_algorithm.present?
        "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
912 913 914 915 916 917 918
      end

      def remove_index_sql(table_name, options = {})
        index_name = index_name_for_remove(table_name, options)
        "DROP INDEX #{index_name}"
      end

919 920
      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)]
921 922
      end

923
      def remove_timestamps_sql(table_name, options = {})
924 925 926
        [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
      end

927 928
      private

929 930 931 932 933 934 935 936 937 938 939 940 941
      # 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

942
      def mariadb?
943
        full_version =~ /mariadb/i
944 945 946
      end

      def supports_rename_index?
947
        mariadb? ? false : version >= '5.7.6'
948 949
      end

950
      def configure_connection
951
        variables = @config.fetch(:variables, {}).stringify_keys
952

953
        # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
954
        variables['sql_auto_is_null'] = 0
955 956 957 958

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

961 962
        defaults = [':default', :default].to_set

963
        # Make MySQL reject illegal values rather than truncating or blanking them, see
964
        # http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
965
        # If the user has provided another value for sql_mode, don't replace it.
966
        unless variables.has_key?('sql_mode') || defaults.include?(@config[:strict])
967
          variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
968 969 970
        end

        # NAMES does not have an equals sign, see
971
        # http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430
972
        # (trailing comma because variable_assignments will always have content)
973 974 975 976 977
        if @config[:encoding]
          encoding = "NAMES #{@config[:encoding]}"
          encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
          encoding << ", "
        end
978 979 980

        # Gather up all of the SET variables...
        variable_assignments = variables.map do |k, v|
981
          if defaults.include?(v)
982
            "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
983
          elsif !v.nil?
984
            "@@SESSION.#{k} = #{quote(v)}"
985 986 987 988 989
          end
          # or else nil; compact to clear nils out
        end.compact.join(', ')

        # ...and send them all in one query
990
        @connection.query  "SET #{encoding} #{variable_assignments}"
991
      end
992 993 994 995 996 997 998 999 1000

      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
1001

1002 1003 1004 1005 1006
      def create_table_info(table_name) # :nodoc:
        @create_table_info_cache = {}
        @create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
      end

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

1011 1012 1013 1014 1015
      def integer_to_sql(limit) # :nodoc:
        case limit
        when 1; 'tinyint'
        when 2; 'smallint'
        when 3; 'mediumint'
1016
        when nil, 4; 'int'
1017
        when 5..8; 'bigint'
1018
        when 11; 'int(11)' # backward compatibility with Rails 2.0
1019 1020 1021 1022 1023 1024 1025 1026 1027 1028
        else raise(ActiveRecordError, "No integer type has byte size #{limit}")
        end
      end

      def text_to_sql(limit) # :nodoc:
        case limit
        when 0..0xff;               'tinytext'
        when nil, 0x100..0xffff;    'text'
        when 0x10000..0xffffff;     'mediumtext'
        when 0x1000000..0xffffffff; 'longtext'
1029
        else raise(ActiveRecordError, "No text type has byte length #{limit}")
1030 1031 1032
        end
      end

1033 1034 1035 1036 1037 1038 1039 1040 1041 1042
      def binary_to_sql(limit) # :nodoc:
        case limit
        when 0..0xff;               'tinyblob'
        when nil, 0x100..0xffff;    'blob'
        when 0x10000..0xffffff;     'mediumblob'
        when 0x1000000..0xffffffff; 'longblob'
        else raise(ActiveRecordError, "No binary type has byte length #{limit}")
        end
      end

1043
      class MysqlJson < Type::Internal::AbstractJson # :nodoc:
1044 1045 1046 1047 1048 1049 1050
        def changed_in_place?(raw_old_value, new_value)
          # Normalization is required because MySQL JSON data format includes
          # the space between the elements.
          super(serialize(deserialize(raw_old_value)), new_value)
        end
      end

1051
      class MysqlString < Type::String # :nodoc:
1052
        def serialize(value)
1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069
          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
1070

1071 1072
      ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql)
      ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2)
1073 1074
      ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql)
      ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
1075 1076
      ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql)
      ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
1077 1078 1079
    end
  end
end