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

11
require 'active_support/core_ext/string/strip'
12 13 14 15

module ActiveRecord
  module ConnectionAdapters
    class AbstractMysqlAdapter < AbstractAdapter
16
      include MySQL::Quoting
17
      include MySQL::ColumnDumper
18

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

23
      def schema_creation # :nodoc:
24
        MySQL::SchemaCreation.new(self)
25 26
      end

27 28 29 30
      def arel_visitor # :nodoc:
        Arel::Visitors::MySQL.new(self)
      end

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

      NATIVE_DATABASE_TYPES = {
42
        primary_key: "int auto_increment PRIMARY KEY",
43 44 45 46 47 48 49 50 51 52 53
        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" },
54 55
      }

56 57 58
      INDEX_TYPES  = [:fulltext, :spatial]
      INDEX_USINGS = [:btree, :hash]

59
      class StatementPool < ConnectionAdapters::StatementPool
60
        private def dealloc(stmt)
61 62 63 64
          stmt[:stmt].close
        end
      end

65
      def initialize(connection, logger, connection_options, config)
66
        super(connection, logger, config)
67

V
Vipul A M 已提交
68
        @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
69

70 71 72
        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 已提交
73 74
      end

75
      CHARSETS_OF_4BYTES_MAXLEN = ['utf8mb4', 'utf16', 'utf16le', 'utf32']
76 77 78 79 80

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

V
Vipul A M 已提交
83
      def version #:nodoc:
84 85 86
        @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
      end

V
Vipul A M 已提交
87 88 89 90
      def mariadb? # :nodoc:
        full_version =~ /mariadb/i
      end

91 92 93 94 95 96 97 98 99
      # Returns true, since this connection adapter supports migrations.
      def supports_migrations?
        true
      end

      def supports_primary_key?
        true
      end

100 101 102 103
      def supports_bulk_alter? #:nodoc:
        true
      end

104 105 106 107 108 109
      # Returns true, since this connection adapter supports prepared statement
      # caching.
      def supports_statement_cache?
        true
      end

110
      # Technically MySQL allows to create indexes with the sort order syntax
111 112 113 114 115
      # but at the moment (5.5) it doesn't yet implement them
      def supports_index_sort_order?
        true
      end

116
      def supports_transaction_isolation?
117
        true
118 119
      end

120 121 122 123
      def supports_explain?
        true
      end

124 125 126 127
      def supports_indexes_in_create?
        true
      end

128 129 130 131
      def supports_foreign_keys?
        true
      end

132
      def supports_views?
133
        true
134 135
      end

136
      def supports_datetime_with_precision?
137 138 139 140 141
        if mariadb?
          version >= '5.3.0'
        else
          version >= '5.6.4'
        end
142 143
      end

144
      def supports_advisory_locks?
145
        true
146 147
      end

148 149
      def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
        select_value("SELECT GET_LOCK('#{lock_name}', #{timeout});").to_s == '1'
150 151
      end

152 153
      def release_advisory_lock(lock_name) # :nodoc:
        select_value("SELECT RELEASE_LOCK('#{lock_name}')").to_s == '1'
154 155
      end

156 157 158 159
      def native_database_types
        NATIVE_DATABASE_TYPES
      end

160 161 162 163
      def index_algorithms
        { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
      end

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

172 173
      def new_column(*args) #:nodoc:
        MySQL::Column.new(*args)
174 175
      end

176
      # Must return the MySQL error number from the exception, if the exception has an
177 178 179 180 181 182 183
      # error number.
      def error_number(exception) # :nodoc:
        raise NotImplementedError
      end

      # REFERENTIAL INTEGRITY ====================================

R
Rafael Mendonça França 已提交
184
      def disable_referential_integrity #:nodoc:
185 186 187 188 189 190 191 192 193 194
        old = select_value("SELECT @@FOREIGN_KEY_CHECKS")

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

195 196 197 198 199 200 201 202
      # CONNECTION MANAGEMENT ====================================

      # Clears the prepared statements cache.
      def clear_cache!
        reload_type_map
        @statements.clear
      end

203
      #--
204
      # DATABASE STATEMENTS ======================================
205
      #++
206

207 208 209 210 211 212
      def explain(arel, binds = [])
        sql     = "EXPLAIN #{to_sql(arel, binds)}"
        start   = Time.now
        result  = exec_query(sql, 'EXPLAIN', binds)
        elapsed = Time.now - start

213
        MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
214 215
      end

216 217
      # Executes the SQL statement in the context of this connection.
      def execute(sql, name = nil)
218
        log(sql, name) { @connection.query(sql) }
219 220
      end

R
Ryuta Kamizono 已提交
221 222 223 224
      # 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:
225 226 227 228 229 230 231
        yield execute(sql, name)
      end

      def begin_db_transaction
        execute "BEGIN"
      end

232 233 234 235 236
      def begin_isolated_db_transaction(isolation)
        execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
        begin_db_transaction
      end

237 238 239 240
      def commit_db_transaction #:nodoc:
        execute "COMMIT"
      end

241
      def exec_rollback_db_transaction #:nodoc:
242 243 244 245 246
        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
247
      # these, we must use a subquery.
248
      def join_to_update(update, select, key) # :nodoc:
249
        if select.limit || select.offset || select.orders.any?
250
          super
251 252 253 254 255 256
        else
          update.table select.source
          update.wheres = select.constraints
        end
      end

J
Jon Leighton 已提交
257 258 259 260
      def empty_insert_statement_value
        "VALUES ()"
      end

261 262 263 264 265 266
      # 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)
267
        sql = create_database(name, options)
268
        reconnect!
269
        sql
270 271 272 273 274 275
      end

      # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
      # Charset defaults to utf8.
      #
      # Example:
A
AvnerCohen 已提交
276
      #   create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
277
      #   create_database 'matt_development'
A
AvnerCohen 已提交
278
      #   create_database 'matt_development', charset: :big5
279 280
      def create_database(name, options = {})
        if options[:collation]
281
          execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')} COLLATE #{quote_table_name(options[:collation])}"
282
        else
283
          execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}"
284 285 286 287 288 289 290 291
        end
      end

      # Drops a MySQL database.
      #
      # Example:
      #   drop_database('sebastian_development')
      def drop_database(name) #:nodoc:
292
        execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
      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 已提交
309
      def tables(name = nil) # :nodoc:
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
        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 已提交
326
        sql = "SELECT table_name FROM information_schema.tables "
R
Ryuta Kamizono 已提交
327
        sql << "WHERE table_schema = #{quote(@config[:database])}"
328

R
Ryuta Kamizono 已提交
329
        select_values(sql, 'SCHEMA')
330 331
      end

332 333 334 335
      def truncate(table_name, name = nil)
        execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
      end

336
      def table_exists?(table_name)
337
        # Update lib/active_record/internal_metadata.rb when this gets removed
338 339 340 341 342 343 344 345 346 347
        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)
348
        return false unless table_name.present?
349

350
        schema, name = extract_schema_qualified_name(table_name)
351

352 353
        sql = "SELECT table_name FROM information_schema.tables "
        sql << "WHERE table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
354

355
        select_values(sql, 'SCHEMA').any?
356 357
      end

358 359 360 361 362 363 364
      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?

365
        schema, name = extract_schema_qualified_name(view_name)
366 367 368 369 370 371 372

        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

373 374 375 376 377 378 379 380 381
      # 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]
382 383 384 385

              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
386
              indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using, row[:Index_comment].presence)
387 388 389 390 391 392 393 394 395 396 397
            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+.
398
      def columns(table_name) # :nodoc:
399 400 401 402 403 404 405 406
        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
407
          new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], comment: field[:Comment].presence)
408 409 410
        end
      end

411
      def table_comment(table_name) # :nodoc:
412 413
        select_value(<<-SQL.strip_heredoc, 'SCHEMA')
          SELECT table_comment
414 415
          FROM information_schema.tables
          WHERE table_name=#{quote(table_name)}
416 417 418
        SQL
      end

419 420
      def create_table(table_name, **options) #:nodoc:
        super(table_name, options: 'ENGINE=InnoDB', **options)
421 422
      end

423
      def bulk_change_table(table_name, operations) #:nodoc:
424
        sqls = operations.flat_map do |command, args|
425 426 427
          table, arguments = args.shift, args
          method = :"#{command}_sql"

428
          if respond_to?(method, true)
429 430 431 432
            send(method, table, *arguments)
          else
            raise "Unknown method called : #{method}(#{arguments.inspect})"
          end
433
        end.join(", ")
434 435 436 437

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

438 439 440 441 442 443
      # 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)}"
444
        rename_table_indexes(table_name, new_name)
445 446
      end

447 448 449 450 451 452
      # 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>]
453
      #   Set to +true+ to only drop the table if it exists.
454 455 456 457
      #   Defaults to false.
      # [<tt>:temporary</tt>]
      #   Set to +true+ to drop temporary table.
      #   Defaults to false.
458 459 460 461
      #
      # 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.
462
      def drop_table(table_name, options = {})
R
Ryuta Kamizono 已提交
463
        create_table_info_cache.delete(table_name) if create_table_info_cache.key?(table_name)
464
        execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
465 466
      end

467
      def rename_index(table_name, old_name, new_name)
468
        if supports_rename_index?
469 470
          validate_index_length!(table_name, new_name)

471 472 473 474 475 476
          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

477 478
      def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
        default = extract_new_default_value(default_or_changes)
479 480 481 482
        column = column_for(table_name, column_name)
        change_column table_name, column_name, column.sql_type, :default => default
      end

483
      def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
        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)}")
499
        rename_column_indexes(table_name, column_name, new_column_name)
500 501
      end

D
doabit 已提交
502
      def add_index(table_name, column_name, options = {}) #:nodoc:
503 504
        index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
        sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
505 506 507 508
        execute add_sql_comment!(sql, comment)
      end

      def add_sql_comment!(sql, comment) # :nodoc:
509
        sql << " COMMENT #{quote(comment)}" if comment
510
        sql
D
doabit 已提交
511 512
      end

513
      def foreign_keys(table_name)
514 515 516 517
        raise ArgumentError unless table_name.present?

        schema, name = extract_schema_qualified_name(table_name)

518
        fk_info = select_all <<-SQL.strip_heredoc
519 520 521 522 523 524
          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
525 526
            AND fk.table_schema = #{quote(schema)}
            AND fk.table_name = #{quote(name)}
527
        SQL
528

529
        create_table_info = create_table_info(table_name)
530 531

        fk_info.map do |row|
532 533 534 535 536 537
          options = {
            column: row['column'],
            name: row['name'],
            primary_key: row['primary_key']
          }

Y
Yves Senn 已提交
538 539
          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")
540

541 542 543 544
          ForeignKeyDefinition.new(table_name, row['to_table'], options)
        end
      end

545
      def table_options(table_name)
546
        create_table_info = create_table_info(table_name)
547 548 549 550 551

        # 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
552 553 554 555 556 557
        raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')

        # strip COMMENT
        raw_table_options.sub!(/ COMMENT='.+'/, '')

        raw_table_options
558 559
      end

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

        sql << ' unsigned' if unsigned && type != :primary_key
        sql
581 582 583 584
      end

      # SHOW VARIABLES LIKE 'name'
      def show_variable(name)
585
        select_value("SELECT @@#{name}", 'SCHEMA')
586 587
      rescue ActiveRecord::StatementInvalid
        nil
588 589
      end

590 591 592
      def primary_keys(table_name) # :nodoc:
        raise ArgumentError unless table_name.present?

593
        schema, name = extract_schema_qualified_name(table_name)
594 595 596 597 598 599 600 601 602

        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
603 604
      end

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

613 614
      def can_perform_case_insensitive_comparison_for?(column)
        column.case_sensitive?
615
      end
616
      private :can_perform_case_insensitive_comparison_for?
617

618 619 620 621 622 623 624 625 626 627 628 629 630 631 632
      # 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

633
      def strict_mode?
634
        self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
635 636
      end

637 638 639 640
      def valid_type?(type)
        !native_database_types[type].nil?
      end

641 642
      protected

S
Sean Griffin 已提交
643
      def initialize_type_map(m) # :nodoc:
644
        super
645

646 647
        register_class_with_limit m, %r(char)i, MysqlString

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

660 661 662 663 664 665
        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

666
        m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
667 668
        m.alias_type %r(year)i,          'integer'
        m.alias_type %r(bit)i,           'binary'
669 670 671 672

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

        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
681 682
      end

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

693
      def extract_precision(sql_type)
694
        if /time/ === sql_type
695 696 697 698 699 700
          super || 0
        else
          super
        end
      end

701
      def fetch_type_metadata(sql_type, extra = "")
702
        MySQL::TypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
S
Sean Griffin 已提交
703 704
      end

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

        return option_strings
      end

718
      def quoted_columns_for_index(column_names, options = {})
719
        option_strings = Hash[column_names.map {|name| [name, '']}]
720

721 722 723 724 725 726 727
        # 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]}
728 729 730 731 732
      end

      def translate_exception(exception, message)
        case error_number(exception)
        when 1062
733
          RecordNotUnique.new(message)
734
        when 1452
735
          InvalidForeignKey.new(message)
736 737
        when 1406
          ValueTooLong.new(message)
738 739 740 741 742 743
        else
          super
        end
      end

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

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

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

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

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

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

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

798 799
      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)]
800 801
      end

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

806 807
      private

808 809 810 811 812 813
      # 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]

814 815 816 817
        # 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?

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

823
      def supports_rename_index?
824
        mariadb? ? false : version >= '5.7.6'
825 826
      end

827
      def configure_connection
828
        variables = @config.fetch(:variables, {}).stringify_keys
829

830
        # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
831
        variables['sql_auto_is_null'] = 0
832 833 834 835

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

838 839
        defaults = [':default', :default].to_set

840
        # Make MySQL reject illegal values rather than truncating or blanking them, see
841
        # http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
842
        # If the user has provided another value for sql_mode, don't replace it.
843 844 845 846 847 848 849 850 851 852
        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
853
          sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')"
854
        end
855
        sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
856 857

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

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

        # ...and send them all in one query
877
        @connection.query  "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
878
      end
879

880 881 882 883 884 885
      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

886 887 888 889 890 891 892 893
      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
894

R
Ryuta Kamizono 已提交
895 896 897 898
      def create_table_info_cache # :nodoc:
        @create_table_info_cache ||= {}
      end

899
      def create_table_info(table_name) # :nodoc:
R
Ryuta Kamizono 已提交
900
        create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
901 902
      end

903 904
      def create_table_definition(*args) # :nodoc:
        MySQL::TableDefinition.new(*args)
905 906
      end

907 908 909 910 911 912
      def extract_schema_qualified_name(string) # :nodoc:
        schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/)
        schema, name = @config[:database], schema unless name
        [schema, name]
      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
          case value
955 956
          when true then MySQL::Quoting::QUOTED_TRUE
          when false then MySQL::Quoting::QUOTED_FALSE
957 958 959 960 961 962 963 964
          else super
          end
        end

        private

        def cast_value(value)
          case value
965 966
          when true then MySQL::Quoting::QUOTED_TRUE
          when false then MySQL::Quoting::QUOTED_FALSE
967 968 969 970
          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