abstract_mysql_adapter.rb 33.6 KB
Newer Older
1
require 'active_record/connection_adapters/abstract_adapter'
2
require 'active_record/connection_adapters/mysql/column'
3
require 'active_record/connection_adapters/mysql/explain_pretty_printer'
4
require 'active_record/connection_adapters/mysql/schema_creation'
5
require 'active_record/connection_adapters/mysql/schema_definitions'
6
require 'active_record/connection_adapters/mysql/schema_dumper'
7
require 'active_record/connection_adapters/mysql/type_metadata'
8

9
require 'active_support/core_ext/string/strip'
10 11 12 13

module ActiveRecord
  module ConnectionAdapters
    class AbstractMysqlAdapter < AbstractAdapter
14
      include MySQL::ColumnDumper
15 16
      include Savepoints

17
      def update_table_definition(table_name, base) # :nodoc:
18
        MySQL::Table.new(table_name, base)
19 20
      end

21
      def schema_creation
22
        MySQL::SchemaCreation.new(self)
23 24
      end

25 26
      ##
      # :singleton-method:
27
      # By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
R
Ryuta Kamizono 已提交
28
      # as boolean. If you wish to disable this emulation you can add the following line
29 30
      # to your application.rb file:
      #
R
Ryuta Kamizono 已提交
31
      #   ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
32 33 34 35 36 37
      class_attribute :emulate_booleans
      self.emulate_booleans = true

      QUOTED_TRUE, QUOTED_FALSE = '1', '0'

      NATIVE_DATABASE_TYPES = {
38
        primary_key: "int auto_increment PRIMARY KEY",
39 40 41 42 43 44 45 46 47 48 49
        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 },
        json:        { name: "json" },
50 51
      }

52 53 54
      INDEX_TYPES  = [:fulltext, :spatial]
      INDEX_USINGS = [:btree, :hash]

55
      def initialize(connection, logger, connection_options, config)
56
        super(connection, logger, config)
57
        @quoted_column_names, @quoted_table_names = {}, {}
58

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

61
        if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
62
          @prepared_statements = true
63
          @visitor.extend(DetermineIfPreparableVisitor)
64
        else
A
Aaron Patterson 已提交
65 66
          @prepared_statements = false
        end
67 68 69 70

        if version < '5.0.0'
          raise "Your version of MySQL (#{full_version.match(/^\d+\.\d+\.\d+/)[0]}) is too old. Active Record supports MySQL >= 5.0."
        end
A
Aaron Patterson 已提交
71 72
      end

73
      CHARSETS_OF_4BYTES_MAXLEN = ['utf8mb4', 'utf16', 'utf16le', 'utf32']
74 75 76 77 78

      def internal_string_options_for_primary_key # :nodoc:
        super.tap { |options|
          options[:collation] = collation.sub(/\A[^_]+/, 'utf8') if CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
        }
79 80
      end

81 82 83 84
      def version
        @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
      end

85 86 87 88 89 90 91 92 93
      # Returns true, since this connection adapter supports migrations.
      def supports_migrations?
        true
      end

      def supports_primary_key?
        true
      end

94 95 96 97
      def supports_bulk_alter? #:nodoc:
        true
      end

98
      # Technically MySQL allows to create indexes with the sort order syntax
99 100 101 102 103
      # but at the moment (5.5) it doesn't yet implement them
      def supports_index_sort_order?
        true
      end

104
      def supports_transaction_isolation?
105
        true
106 107
      end

108 109 110 111
      def supports_explain?
        true
      end

112 113 114 115
      def supports_indexes_in_create?
        true
      end

116 117 118 119
      def supports_foreign_keys?
        true
      end

120
      def supports_views?
121
        true
122 123
      end

124
      def supports_datetime_with_precision?
125
        version >= '5.6.4'
126 127
      end

128
      def supports_advisory_locks?
129
        true
130 131
      end

132 133
      def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
        select_value("SELECT GET_LOCK('#{lock_name}', #{timeout});").to_s == '1'
134 135
      end

136 137
      def release_advisory_lock(lock_name) # :nodoc:
        select_value("SELECT RELEASE_LOCK('#{lock_name}')").to_s == '1'
138 139
      end

140 141 142 143
      def native_database_types
        NATIVE_DATABASE_TYPES
      end

144 145 146 147
      def index_algorithms
        { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
      end

148 149 150 151 152 153 154 155
      # 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

156 157
      def new_column(field, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil) # :nodoc:
        MySQL::Column.new(field, default, sql_type_metadata, null, table_name, default_function, collation)
158 159
      end

160
      # Must return the MySQL error number from the exception, if the exception has an
161 162 163 164 165 166 167
      # error number.
      def error_number(exception) # :nodoc:
        raise NotImplementedError
      end

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

168 169 170
      def _quote(value) # :nodoc:
        if value.is_a?(Type::Binary::Data)
          "x'#{value.hex}'"
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
        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

188 189 190 191
      def unquoted_true
        1
      end

192 193 194 195
      def quoted_false
        QUOTED_FALSE
      end

196 197 198 199
      def unquoted_false
        0
      end

200 201 202 203 204 205 206 207
      def quoted_date(value)
        if supports_datetime_with_precision?
          super
        else
          super.sub(/\.\d{6}\z/, '')
        end
      end

208 209
      # REFERENTIAL INTEGRITY ====================================

R
Rafael Mendonça França 已提交
210
      def disable_referential_integrity #:nodoc:
211 212 213 214 215 216 217 218 219 220
        old = select_value("SELECT @@FOREIGN_KEY_CHECKS")

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

221
      #--
222
      # DATABASE STATEMENTS ======================================
223
      #++
224

225 226 227 228 229 230
      def explain(arel, binds = [])
        sql     = "EXPLAIN #{to_sql(arel, binds)}"
        start   = Time.now
        result  = exec_query(sql, 'EXPLAIN', binds)
        elapsed = Time.now - start

231
        MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
232 233
      end

234
      def clear_cache!
235
        super
236 237 238
        reload_type_map
      end

239 240
      # Executes the SQL statement in the context of this connection.
      def execute(sql, name = nil)
241
        log(sql, name) { @connection.query(sql) }
242 243
      end

R
Ryuta Kamizono 已提交
244 245 246 247
      # Mysql2Adapter doesn't have to free a result after using it, but we use this method
      # to write stuff in an abstract way without concerning ourselves about whether it
      # needs to be explicitly freed or not.
      def execute_and_free(sql, name = nil) # :nodoc:
248 249 250 251 252 253 254
        yield execute(sql, name)
      end

      def begin_db_transaction
        execute "BEGIN"
      end

255 256 257 258 259
      def begin_isolated_db_transaction(isolation)
        execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
        begin_db_transaction
      end

260 261 262 263
      def commit_db_transaction #:nodoc:
        execute "COMMIT"
      end

264
      def exec_rollback_db_transaction #:nodoc:
265 266 267 268 269
        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
270
      # these, we must use a subquery.
271
      def join_to_update(update, select, key) # :nodoc:
272
        if select.limit || select.offset || select.orders.any?
273
          super
274 275 276 277 278 279
        else
          update.table select.source
          update.wheres = select.constraints
        end
      end

J
Jon Leighton 已提交
280 281 282 283
      def empty_insert_statement_value
        "VALUES ()"
      end

284 285 286 287 288 289
      # 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)
290
        sql = create_database(name, options)
291
        reconnect!
292
        sql
293 294 295 296 297 298
      end

      # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
      # Charset defaults to utf8.
      #
      # Example:
A
AvnerCohen 已提交
299
      #   create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
300
      #   create_database 'matt_development'
A
AvnerCohen 已提交
301
      #   create_database 'matt_development', charset: :big5
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
      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 已提交
332
      def tables(name = nil) # :nodoc:
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
        ActiveSupport::Deprecation.warn(<<-MSG.squish)
          #tables currently returns both tables and views.
          This behavior is deprecated and will be changed with Rails 5.1 to only return tables.
          Use #data_sources instead.
        MSG

        if name
          ActiveSupport::Deprecation.warn(<<-MSG.squish)
            Passing arguments to #tables is deprecated without replacement.
          MSG
        end

        data_sources
      end

      def data_sources
M
Matt Jones 已提交
349
        sql = "SELECT table_name FROM information_schema.tables "
R
Ryuta Kamizono 已提交
350
        sql << "WHERE table_schema = #{quote(@config[:database])}"
351

R
Ryuta Kamizono 已提交
352
        select_values(sql, 'SCHEMA')
353 354
      end

355 356 357 358
      def truncate(table_name, name = nil)
        execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
      end

359
      def table_exists?(table_name)
360
        # Update lib/active_record/internal_metadata.rb when this gets removed
361 362 363 364 365 366 367 368 369 370
        ActiveSupport::Deprecation.warn(<<-MSG.squish)
          #table_exists? currently checks both tables and views.
          This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
          Use #data_source_exists? instead.
        MSG

        data_source_exists?(table_name)
      end

      def data_source_exists?(table_name)
371
        return false unless table_name.present?
372

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

376 377
        sql = "SELECT table_name FROM information_schema.tables "
        sql << "WHERE table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
378

379
        select_values(sql, 'SCHEMA').any?
380 381
      end

382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
      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

398 399 400 401 402 403 404 405 406
      # 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]
407 408 409 410 411

              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)
412 413 414 415 416 417 418 419 420 421 422
            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+.
423
      def columns(table_name) # :nodoc:
424 425 426 427 428 429 430 431
        table_name = table_name.to_s
        column_definitions(table_name).map do |field|
          type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
          if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
            default, default_function = nil, field[:Default]
          else
            default, default_function = field[:Default], nil
          end
432
          new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation])
433 434 435 436
        end
      end

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

440
      def bulk_change_table(table_name, operations) #:nodoc:
441
        sqls = operations.flat_map do |command, args|
442 443 444
          table, arguments = args.shift, args
          method = :"#{command}_sql"

445
          if respond_to?(method, true)
446 447 448 449
            send(method, table, *arguments)
          else
            raise "Unknown method called : #{method}(#{arguments.inspect})"
          end
450
        end.join(", ")
451 452 453 454

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

455 456 457 458 459 460
      # 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)}"
461
        rename_table_indexes(table_name, new_name)
462 463
      end

464 465 466 467 468 469
      # 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>]
470
      #   Set to +true+ to only drop the table if it exists.
471 472 473 474
      #   Defaults to false.
      # [<tt>:temporary</tt>]
      #   Set to +true+ to drop temporary table.
      #   Defaults to false.
475 476 477 478
      #
      # 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.
479
      def drop_table(table_name, options = {})
R
Ryuta Kamizono 已提交
480
        create_table_info_cache.delete(table_name) if create_table_info_cache.key?(table_name)
481
        execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
482 483
      end

484
      def rename_index(table_name, old_name, new_name)
485
        if supports_rename_index?
486 487
          validate_index_length!(table_name, new_name)

488 489 490 491 492 493
          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

494 495
      def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
        default = extract_new_default_value(default_or_changes)
496 497 498 499
        column = column_for(table_name, column_name)
        change_column table_name, column_name, column.sql_type, :default => default
      end

500
      def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
        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)}")
516
        rename_column_indexes(table_name, column_name, new_column_name)
517 518
      end

D
doabit 已提交
519
      def add_index(table_name, column_name, options = {}) #:nodoc:
520 521
        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 已提交
522 523
      end

524
      def foreign_keys(table_name)
525
        fk_info = select_all <<-SQL.strip_heredoc
526 527 528 529 530 531 532 533
          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}'
534
        SQL
535

536
        create_table_info = create_table_info(table_name)
537 538

        fk_info.map do |row|
539 540 541 542 543 544
          options = {
            column: row['column'],
            name: row['name'],
            primary_key: row['primary_key']
          }

Y
Yves Senn 已提交
545 546
          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")
547

548 549 550 551
          ForeignKeyDefinition.new(table_name, row['to_table'], options)
        end
      end

552
      def table_options(table_name)
553
        create_table_info = create_table_info(table_name)
554 555 556 557 558 559 560 561

        # 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

562
      # Maps logical Rails types to MySQL-specific data types.
563 564
      def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil)
        sql = case type.to_s
565
        when 'integer'
566
          integer_to_sql(limit)
567
        when 'text'
568
          text_to_sql(limit)
569 570 571 572 573 574 575 576
        when 'blob'
          binary_to_sql(limit)
        when 'binary'
          if (0..0xfff) === limit
            "varbinary(#{limit})"
          else
            binary_to_sql(limit)
          end
577
        else
578
          super(type, limit, precision, scale)
579
        end
580 581 582

        sql << ' unsigned' if unsigned && type != :primary_key
        sql
583 584 585 586
      end

      # SHOW VARIABLES LIKE 'name'
      def show_variable(name)
587 588 589 590
        variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
        variables.first['Value'] unless variables.empty?
      rescue ActiveRecord::StatementInvalid
        nil
591 592
      end

593 594 595 596 597 598 599 600 601 602 603 604 605 606
      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
607 608
      end

609
      def case_sensitive_comparison(table, attribute, column, value)
610
        if !value.nil? && column.collation && !column.case_sensitive?
611
          table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new))
612 613
        else
          super
614 615 616
        end
      end

617 618
      def can_perform_case_insensitive_comparison_for?(column)
        column.case_sensitive?
619
      end
620
      private :can_perform_case_insensitive_comparison_for?
621

622 623 624 625 626 627 628 629 630 631 632 633 634 635 636
      # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
      # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
      # distinct queries, and requires that the ORDER BY include the distinct column.
      # See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
      def columns_for_distinct(columns, orders) # :nodoc:
        order_columns = orders.reject(&:blank?).map { |s|
          # Convert Arel node to string
          s = s.to_sql unless s.is_a?(String)
          # Remove any ASC/DESC modifiers
          s.gsub(/\s+(?:ASC|DESC)\b/i, '')
        }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }

        [super, *order_columns].join(', ')
      end

637
      def strict_mode?
638
        self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
639 640
      end

641 642 643 644
      def valid_type?(type)
        !native_database_types[type].nil?
      end

645 646
      protected

S
Sean Griffin 已提交
647
      def initialize_type_map(m) # :nodoc:
648
        super
649

650 651
        register_class_with_limit m, %r(char)i, MysqlString

652 653 654 655 656 657
        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)
658 659
        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 已提交
660 661
        m.register_type %r(^float)i,     Type::Float.new(limit: 24)
        m.register_type %r(^double)i,    Type::Float.new(limit: 53)
662
        m.register_type %r(^json)i,      MysqlJson.new
S
Sean Griffin 已提交
663

664 665 666 667 668 669
        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

670
        m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
671 672
        m.alias_type %r(year)i,          'integer'
        m.alias_type %r(bit)i,           'binary'
673 674 675 676

        m.register_type(%r(enum)i) do |sql_type|
          limit = sql_type[/^enum\((.+)\)/i, 1]
            .split(',').map{|enum| enum.strip.length - 2}.max
677
          MysqlString.new(limit: limit)
678
        end
679 680 681 682 683 684

        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
685 686
      end

687 688
      def register_integer_type(mapping, key, options) # :nodoc:
        mapping.register_type(key) do |sql_type|
689
          if /\bunsigned\z/ === sql_type
690 691 692 693 694 695 696
            Type::UnsignedInteger.new(options)
          else
            Type::Integer.new(options)
          end
        end
      end

697
      def extract_precision(sql_type)
698
        if /time/ === sql_type
699 700 701 702 703 704
          super || 0
        else
          super
        end
      end

705
      def fetch_type_metadata(sql_type, extra = "")
706
        MySQL::TypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
S
Sean Griffin 已提交
707 708
      end

709 710 711 712
      def add_index_length(option_strings, column_names, options = {})
        if options.is_a?(Hash) && length = options[:length]
          case length
          when Hash
713
            column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
714 715 716 717 718 719 720 721
          when Fixnum
            column_names.each {|name| option_strings[name] += "(#{length})"}
          end
        end

        return option_strings
      end

722
      def quoted_columns_for_index(column_names, options = {})
723
        option_strings = Hash[column_names.map {|name| [name, '']}]
724

725 726 727 728 729 730 731
        # 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]}
732 733 734 735 736
      end

      def translate_exception(exception, message)
        case error_number(exception)
        when 1062
737
          RecordNotUnique.new(message)
738
        when 1452
739
          InvalidForeignKey.new(message)
740 741 742 743 744 745
        else
          super
        end
      end

      def add_column_sql(table_name, column_name, type, options = {})
746
        td = create_table_definition(table_name)
747
        cd = td.new_column_definition(column_name, type, options)
748
        schema_creation.accept(AddColumnDefinition.new(cd))
749 750 751 752 753 754 755 756 757 758 759 760 761
      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

762 763 764
        td = create_table_definition(table_name)
        cd = td.new_column_definition(column.name, type, options)
        schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
765 766 767
      end

      def rename_column_sql(table_name, column_name, new_column_name)
768
        column  = column_for(table_name, column_name)
769 770 771
        options = {
          default: column.default,
          null: column.null,
772
          auto_increment: column.auto_increment?
773
        }
774

K
kennyj 已提交
775
        current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
776 777 778
        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))
779 780
      end

M
Marc-Andre Lafortune 已提交
781 782 783 784 785 786
      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) }
787 788 789
      end

      def add_index_sql(table_name, column_name, options = {})
790 791 792
        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}"
793 794 795 796 797 798 799
      end

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

800 801
      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)]
802 803
      end

804
      def remove_timestamps_sql(table_name, options = {})
805 806 807
        [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
      end

808 809
      private

810 811 812 813 814 815
      # 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]

816 817 818 819
        # Materialize subquery by adding distinct
        # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
        subsubselect.distinct unless select.limit || select.offset || select.orders.any?

820 821
        subselect = Arel::SelectManager.new(select.engine)
        subselect.project Arel.sql(key.name)
822
        subselect.from subsubselect.as('__active_record_temp')
823 824
      end

825
      def mariadb?
826
        full_version =~ /mariadb/i
827 828 829
      end

      def supports_rename_index?
830
        mariadb? ? false : version >= '5.7.6'
831 832
      end

833
      def configure_connection
834
        variables = @config.fetch(:variables, {}).stringify_keys
835

836
        # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
837
        variables['sql_auto_is_null'] = 0
838 839 840 841

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

844 845
        defaults = [':default', :default].to_set

846
        # Make MySQL reject illegal values rather than truncating or blanking them, see
847
        # http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
848
        # If the user has provided another value for sql_mode, don't replace it.
849 850 851 852 853 854 855 856 857 858
        if sql_mode = variables.delete('sql_mode')
          sql_mode = quote(sql_mode)
        elsif !defaults.include?(strict_mode?)
          if strict_mode?
            sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')"
          else
            sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')"
            sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')"
            sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')"
          end
859
          sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')"
860
        end
861
        sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
862 863

        # NAMES does not have an equals sign, see
864
        # http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430
865
        # (trailing comma because variable_assignments will always have content)
866 867 868 869 870
        if @config[:encoding]
          encoding = "NAMES #{@config[:encoding]}"
          encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
          encoding << ", "
        end
871 872 873

        # Gather up all of the SET variables...
        variable_assignments = variables.map do |k, v|
874
          if defaults.include?(v)
875
            "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
876
          elsif !v.nil?
877
            "@@SESSION.#{k} = #{quote(v)}"
878 879 880 881 882
          end
          # or else nil; compact to clear nils out
        end.compact.join(', ')

        # ...and send them all in one query
883
        @connection.query  "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
884
      end
885

886 887 888 889 890 891
      def column_definitions(table_name) # :nodoc:
        execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
          each_hash(result)
        end
      end

892 893 894 895 896 897 898 899
      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
900

R
Ryuta Kamizono 已提交
901 902 903 904
      def create_table_info_cache # :nodoc:
        @create_table_info_cache ||= {}
      end

905
      def create_table_info(table_name) # :nodoc:
R
Ryuta Kamizono 已提交
906
        create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
907 908
      end

909
      def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
910
        MySQL::TableDefinition.new(name, temporary, options, as)
911 912
      end

913 914 915 916 917
      def integer_to_sql(limit) # :nodoc:
        case limit
        when 1; 'tinyint'
        when 2; 'smallint'
        when 3; 'mediumint'
918
        when nil, 4; 'int'
919 920 921 922 923 924 925 926 927 928 929
        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'
930
        else raise(ActiveRecordError, "No text type has byte length #{limit}")
931 932 933
        end
      end

934 935 936 937 938 939 940 941 942 943
      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

944
      class MysqlJson < Type::Internal::AbstractJson # :nodoc:
945 946 947 948 949 950 951
        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

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

972
      ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2)
973
      ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
974
      ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
975 976 977
    end
  end
end