abstract_mysql_adapter.rb 35.9 KB
Newer Older
1
require 'active_support/core_ext/string/strip'
2 3 4 5

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

8 9 10
      module ColumnMethods
        def primary_key(name, type = :primary_key, **options)
          options[:auto_increment] = true if type == :bigint
11 12
          super
        end
13 14 15 16

        def json(*args, **options)
          args.each { |name| column(name, :json, options) }
        end
17 18
      end

19
      class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
20
        attr_accessor :charset
21 22
      end

23 24
      class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
        include ColumnMethods
25 26 27 28 29 30 31 32

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

        private

        def create_column_definition(name, type)
          ColumnDefinition.new(name, type)
        end
42 43
      end

44 45 46 47
      class Table < ActiveRecord::ConnectionAdapters::Table
        include ColumnMethods
      end

48
      class SchemaCreation < AbstractAdapter::SchemaCreation
49 50
        private

51 52 53 54
        def visit_DropForeignKey(name)
          "DROP FOREIGN KEY #{name}"
        end

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

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

68 69 70 71
        def visit_AddColumnDefinition(o)
          add_column_position!(super, column_options(o.column))
        end

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

77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
        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

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

        def index_in_create(table_name, column_name, options)
103 104
          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}) "
105
        end
106 107
      end

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

112 113 114 115
      def schema_creation
        SchemaCreation.new self
      end

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

128 129 130 131 132 133 134 135
      private

      def schema_limit(column)
        super unless column.type == :boolean
      end

      def schema_precision(column)
        super unless /time/ === column.sql_type && column.precision == 0
136 137 138
      end

      def schema_collation(column)
139 140 141
        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"]
142
          column.collation.inspect if column.collation != @collation_cache[table_name]
143 144
        end
      end
145 146

      public
147

148
      class Column < ConnectionAdapters::Column # :nodoc:
149
        delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true
150

S
Sean Griffin 已提交
151 152
        def initialize(*)
          super
153
          assert_valid_default(default)
154
          extract_default
155 156
        end

157 158 159
        def extract_default
          if blob_or_text_column?
            @default = null || strict ? nil : ''
S
Sean Griffin 已提交
160
          elsif missing_default_forged_as_empty_string?(default)
161
            @default = nil
162 163 164 165
          end
        end

        def has_default?
166
          return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
167 168
          super
        end
169

170 171 172
        def blob_or_text_column?
          sql_type =~ /blob/i || type == :text
        end
173

174 175 176 177
        def case_sensitive?
          collation && !collation.match(/_ci$/)
        end

178 179 180 181
        def auto_increment?
          extra == 'auto_increment'
        end

182 183 184 185 186 187 188 189 190 191 192 193
        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
194 195 196 197 198 199

        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 已提交
200 201 202
      end

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

205
        def initialize(type_metadata, extra: "", strict: false)
S
Sean Griffin 已提交
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
          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
223 224

        def attributes_for_hash
225
          [self.class, @type_metadata, extra, strict]
226
        end
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
      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 = {
249 250 251 252 253 254 255 256 257 258 259 260 261
        primary_key: "int(11) auto_increment PRIMARY KEY",
        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 },
        bigint:      { name: "bigint" },
        json:        { name: "json" },
262 263
      }

264 265 266
      INDEX_TYPES  = [:fulltext, :spatial]
      INDEX_USINGS = [:btree, :hash]

267 268 269 270 271
      # 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 = {}, {}
272

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

275
        if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
276
          @prepared_statements = true
277
        else
A
Aaron Patterson 已提交
278 279 280 281
          @prepared_statements = false
        end
      end

282 283 284 285 286 287 288 289 290 291
      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

292 293 294 295 296 297 298 299 300
      # Returns true, since this connection adapter supports migrations.
      def supports_migrations?
        true
      end

      def supports_primary_key?
        true
      end

301 302 303 304
      def supports_bulk_alter? #:nodoc:
        true
      end

305
      # Technically MySQL allows to create indexes with the sort order syntax
306 307 308 309 310
      # but at the moment (5.5) it doesn't yet implement them
      def supports_index_sort_order?
        true
      end

311 312 313 314 315
      # 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?
316
        version >= '5.0.0'
317 318
      end

319 320 321 322
      def supports_indexes_in_create?
        true
      end

323 324 325 326
      def supports_foreign_keys?
        true
      end

327
      def supports_views?
328
        version >= '5.0.0'
329 330
      end

331
      def supports_datetime_with_precision?
332
        version >= '5.6.4'
333 334
      end

335 336 337 338
      def native_database_types
        NATIVE_DATABASE_TYPES
      end

339 340 341 342
      def index_algorithms
        { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
      end

343 344 345 346 347 348 349 350
      # 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

351 352
      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)
353 354
      end

355
      # Must return the MySQL error number from the exception, if the exception has an
356 357 358 359 360 361 362
      # error number.
      def error_number(exception) # :nodoc:
        raise NotImplementedError
      end

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

363 364 365
      def _quote(value) # :nodoc:
        if value.is_a?(Type::Binary::Data)
          "x'#{value.hex}'"
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
        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

383 384 385 386
      def unquoted_true
        1
      end

387 388 389 390
      def quoted_false
        QUOTED_FALSE
      end

391 392 393 394
      def unquoted_false
        0
      end

395 396 397 398 399 400 401 402
      def quoted_date(value)
        if supports_datetime_with_precision?
          super
        else
          super.sub(/\.\d{6}\z/, '')
        end
      end

403 404
      # REFERENTIAL INTEGRITY ====================================

R
Rafael Mendonça França 已提交
405
      def disable_referential_integrity #:nodoc:
406 407 408 409 410 411 412 413 414 415
        old = select_value("SELECT @@FOREIGN_KEY_CHECKS")

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

416
      #--
417
      # DATABASE STATEMENTS ======================================
418
      #++
419

420 421 422 423 424
      def clear_cache!
        super
        reload_type_map
      end

425 426
      # Executes the SQL statement in the context of this connection.
      def execute(sql, name = nil)
427
        log(sql, name) { @connection.query(sql) }
428 429 430
      end

      # MysqlAdapter has to free a result after using it, so we use this method to write
431
      # stuff in an abstract way without concerning ourselves about whether it needs to be
432 433 434 435 436 437 438 439 440 441 442 443 444 445
      # 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

446 447 448 449 450
      def begin_isolated_db_transaction(isolation)
        execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
        begin_db_transaction
      end

451 452 453 454
      def commit_db_transaction #:nodoc:
        execute "COMMIT"
      end

455
      def exec_rollback_db_transaction #:nodoc:
456 457 458 459 460
        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
461
      # these, we must use a subquery.
462 463
      def join_to_update(update, select) #:nodoc:
        if select.limit || select.offset || select.orders.any?
464
          super
465 466 467 468 469 470
        else
          update.table select.source
          update.wheres = select.constraints
        end
      end

J
Jon Leighton 已提交
471 472 473 474
      def empty_insert_statement_value
        "VALUES ()"
      end

475 476 477 478 479 480
      # 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)
481
        sql = create_database(name, options)
482
        reconnect!
483
        sql
484 485 486 487 488 489
      end

      # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
      # Charset defaults to utf8.
      #
      # Example:
A
AvnerCohen 已提交
490
      #   create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
491
      #   create_database 'matt_development'
A
AvnerCohen 已提交
492
      #   create_database 'matt_development', charset: :big5
493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
      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

523 524
      def tables(name = nil, database = nil, like = nil) #:nodoc:
        sql = "SHOW TABLES "
525
        sql << "IN #{quote_table_name(database)} " if database
526
        sql << "LIKE #{quote(like)}" if like
527 528

        execute_and_free(sql, 'SCHEMA') do |result|
529
          result.collect(&:first)
530 531 532
        end
      end

533 534 535 536
      def truncate(table_name, name = nil)
        execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
      end

537
      def table_exists?(name)
538
        return false unless name.present?
539
        return true if tables(nil, nil, name).any?
540 541 542 543 544 545 546 547 548

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

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

549
        tables(nil, schema, table).any?
550 551 552 553 554 555 556 557 558 559 560
      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]
561 562 563 564 565

              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)
566 567 568 569 570 571 572 573 574 575 576
            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+.
577
      def columns(table_name)#:nodoc:
578
        sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
579 580
        execute_and_free(sql, 'SCHEMA') do |result|
          each_hash(result).map do |field|
581
            field_name = set_field_encoding(field[:Field])
582
            sql_type = field[:Type]
583 584
            type_metadata = fetch_type_metadata(sql_type, field[:Extra])
            new_column(field_name, field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
585 586 587 588 589 590 591 592
          end
        end
      end

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

593
      def bulk_change_table(table_name, operations) #:nodoc:
594
        sqls = operations.flat_map do |command, args|
595 596 597
          table, arguments = args.shift, args
          method = :"#{command}_sql"

598
          if respond_to?(method, true)
599 600 601 602
            send(method, table, *arguments)
          else
            raise "Unknown method called : #{method}(#{arguments.inspect})"
          end
603
        end.join(", ")
604 605 606 607

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

608 609 610 611 612 613
      # 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)}"
614
        rename_table_indexes(table_name, new_name)
615 616
      end

617 618 619 620 621 622
      # 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>]
623
      #   Set to +true+ to only drop the table if it exists.
624 625 626 627
      #   Defaults to false.
      # [<tt>:temporary</tt>]
      #   Set to +true+ to drop temporary table.
      #   Defaults to false.
628 629 630 631
      #
      # 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.
632
      def drop_table(table_name, options = {})
633
        execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
634 635
      end

636
      def rename_index(table_name, old_name, new_name)
637
        if supports_rename_index?
638 639
          validate_index_length!(table_name, new_name)

640 641 642 643 644 645
          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

646 647
      def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
        default = extract_new_default_value(default_or_changes)
648 649 650 651
        column = column_for(table_name, column_name)
        change_column table_name, column_name, column.sql_type, :default => default
      end

652
      def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
653 654 655 656 657 658 659 660 661 662 663 664 665 666 667
        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)}")
668
        rename_column_indexes(table_name, column_name, new_column_name)
669 670
      end

D
doabit 已提交
671
      def add_index(table_name, column_name, options = {}) #:nodoc:
672 673
        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 已提交
674 675
      end

676
      def foreign_keys(table_name)
677
        fk_info = select_all <<-SQL.strip_heredoc
678 679 680 681 682 683 684 685
          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}'
686
        SQL
687 688 689 690

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

        fk_info.map do |row|
691 692 693 694 695 696
          options = {
            column: row['column'],
            name: row['name'],
            primary_key: row['primary_key']
          }

Y
Yves Senn 已提交
697 698
          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")
699

700 701 702 703
          ForeignKeyDefinition.new(table_name, row['to_table'], options)
        end
      end

704 705 706 707 708 709 710 711 712 713
      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

714 715
      # Maps logical Rails types to MySQL-specific data types.
      def type_to_sql(type, limit = nil, precision = nil, scale = nil)
716
        case type.to_s
717
        when 'binary'
718
          binary_to_sql(limit)
719
        when 'integer'
720
          integer_to_sql(limit)
721
        when 'text'
722
          text_to_sql(limit)
723 724
        else
          super
725 726 727 728 729
        end
      end

      # SHOW VARIABLES LIKE 'name'
      def show_variable(name)
730 731 732 733
        variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
        variables.first['Value'] unless variables.empty?
      rescue ActiveRecord::StatementInvalid
        nil
734 735 736 737
      end

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

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

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

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

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

780 781 782 783
      def valid_type?(type)
        !native_database_types[type].nil?
      end

784 785
      protected

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

789 790
        register_class_with_limit m, %r(char)i, MysqlString

791 792 793 794 795 796
        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)
797 798
        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 已提交
799 800
        m.register_type %r(^float)i,     Type::Float.new(limit: 24)
        m.register_type %r(^double)i,    Type::Float.new(limit: 53)
801
        m.register_type %r(^json)i,      MysqlJson.new
S
Sean Griffin 已提交
802

803 804 805 806 807 808
        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

809 810 811 812
        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'
813 814 815 816

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

821 822 823 824 825 826 827 828 829 830
      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

831
      def extract_precision(sql_type)
832
        if /time/ === sql_type
833 834 835 836 837 838
          super || 0
        else
          super
        end
      end

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

843 844 845 846
      def add_index_length(option_strings, column_names, options = {})
        if options.is_a?(Hash) && length = options[:length]
          case length
          when Hash
847
            column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
848 849 850 851 852 853 854 855
          when Fixnum
            column_names.each {|name| option_strings[name] += "(#{length})"}
          end
        end

        return option_strings
      end

856
      def quoted_columns_for_index(column_names, options = {})
857
        option_strings = Hash[column_names.map {|name| [name, '']}]
858

859 860 861 862 863 864 865
        # 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]}
866 867 868 869 870 871 872 873 874 875 876 877 878 879
      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 = {})
880
        td = create_table_definition(table_name)
881
        cd = td.new_column_definition(column_name, type, options)
882
        schema_creation.accept(AddColumnDefinition.new(cd))
883 884 885 886 887 888 889 890 891 892 893 894 895
      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

896 897 898
        td = create_table_definition(table_name)
        cd = td.new_column_definition(column.name, type, options)
        schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
899 900 901
      end

      def rename_column_sql(table_name, column_name, new_column_name)
902
        column  = column_for(table_name, column_name)
903 904 905
        options = {
          default: column.default,
          null: column.null,
906
          auto_increment: column.auto_increment?
907
        }
908

K
kennyj 已提交
909
        current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
910 911 912
        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))
913 914
      end

M
Marc-Andre Lafortune 已提交
915 916 917 918 919 920
      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) }
921 922 923
      end

      def add_index_sql(table_name, column_name, options = {})
924 925 926
        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}"
927 928 929 930 931 932 933
      end

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

934 935
      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)]
936 937
      end

938
      def remove_timestamps_sql(table_name, options = {})
939 940 941
        [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
      end

942 943
      private

944 945 946 947 948 949 950 951 952 953 954 955 956
      # 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

957
      def version
958
        @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
959 960 961
      end

      def mariadb?
962
        full_version =~ /mariadb/i
963 964 965
      end

      def supports_rename_index?
966
        mariadb? ? false : version >= '5.7.6'
967 968
      end

969
      def configure_connection
970
        variables = @config.fetch(:variables, {}).stringify_keys
971

972
        # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
973
        variables['sql_auto_is_null'] = 0
974 975 976 977

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

980 981
        defaults = [':default', :default].to_set

982
        # Make MySQL reject illegal values rather than truncating or blanking them, see
983
        # http://dev.mysql.com/doc/refman/5.6/en/sql-mode.html#sqlmode_strict_all_tables
984
        # If the user has provided another value for sql_mode, don't replace it.
985
        unless variables.has_key?('sql_mode') || defaults.include?(@config[:strict])
986
          variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
987 988 989
        end

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

        # Gather up all of the SET variables...
        variable_assignments = variables.map do |k, v|
1000
          if defaults.include?(v)
1001
            "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
1002
          elsif !v.nil?
1003
            "@@SESSION.#{k} = #{quote(v)}"
1004 1005 1006 1007 1008
          end
          # or else nil; compact to clear nils out
        end.compact.join(', ')

        # ...and send them all in one query
1009
        @connection.query  "SET #{encoding} #{variable_assignments}"
1010
      end
1011 1012 1013 1014 1015 1016 1017 1018 1019

      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
1020

1021
      def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
1022 1023 1024
        TableDefinition.new(native_database_types, name, temporary, options, as)
      end

1025 1026 1027 1028 1029
      def binary_to_sql(limit) # :nodoc:
        case limit
        when 0..0xfff;           "varbinary(#{limit})"
        when nil;                "blob"
        when 0x1000..0xffffffff; "blob(#{limit})"
1030
        else raise(ActiveRecordError, "No binary type has byte length #{limit}")
1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050
        end
      end

      def integer_to_sql(limit) # :nodoc:
        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
      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'
1051
        else raise(ActiveRecordError, "No text type has byte length #{limit}")
1052 1053 1054
        end
      end

1055
      class MysqlJson < Type::Internal::AbstractJson # :nodoc:
1056 1057 1058 1059 1060 1061 1062
        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

1063
      class MysqlString < Type::String # :nodoc:
1064
        def serialize(value)
1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081
          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
1082

1083 1084
      ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql)
      ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2)
1085 1086
      ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql)
      ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
1087 1088 1089
    end
  end
end