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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

145 146 147 148 149 150 151 152
      class StatementPool < ConnectionAdapters::StatementPool
        private

        def dealloc(stmt)
          stmt[:stmt].close
        end
      end

153 154 155 156 157
      # 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 = {}, {}
158

A
Aaron Patterson 已提交
159
        @visitor = Arel::Visitors::MySQL.new self
160
        @statements = StatementPool.new(self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
A
Aaron Patterson 已提交
161

162
        if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
163
          @prepared_statements = true
164
          @visitor.extend(DetermineIfPreparableVisitor)
165
        else
A
Aaron Patterson 已提交
166 167 168 169
          @prepared_statements = false
        end
      end

170 171 172 173 174 175 176 177 178 179
      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

180 181 182 183
      def version
        @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
      end

184 185 186 187 188 189 190 191 192
      # Returns true, since this connection adapter supports migrations.
      def supports_migrations?
        true
      end

      def supports_primary_key?
        true
      end

193 194 195 196
      def supports_bulk_alter? #:nodoc:
        true
      end

197 198 199 200 201 202
      # Returns true, since this connection adapter supports prepared statement
      # caching.
      def supports_statement_cache?
        true
      end

203
      # Technically MySQL allows to create indexes with the sort order syntax
204 205 206 207 208
      # but at the moment (5.5) it doesn't yet implement them
      def supports_index_sort_order?
        true
      end

209 210 211 212 213
      # 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?
214
        version >= '5.0.0'
215 216
      end

217 218 219 220
      def supports_explain?
        true
      end

221 222 223 224
      def supports_indexes_in_create?
        true
      end

225 226 227 228
      def supports_foreign_keys?
        true
      end

229
      def supports_views?
230
        version >= '5.0.0'
231 232
      end

233
      def supports_datetime_with_precision?
234
        version >= '5.6.4'
235 236
      end

237 238 239 240 241 242
      # 5.0.0 definitely supports it, possibly supported by earlier versions but
      # not sure
      def supports_advisory_locks?
        version >= '5.0.0'
      end

243 244
      def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
        select_value("SELECT GET_LOCK('#{lock_name}', #{timeout});").to_s == '1'
245 246
      end

247 248
      def release_advisory_lock(lock_name) # :nodoc:
        select_value("SELECT RELEASE_LOCK('#{lock_name}')").to_s == '1'
249 250
      end

251 252 253 254
      def native_database_types
        NATIVE_DATABASE_TYPES
      end

255 256 257 258
      def index_algorithms
        { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
      end

259 260 261 262 263 264 265 266
      # 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

267 268
      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)
269 270
      end

271
      # Must return the MySQL error number from the exception, if the exception has an
272 273 274 275 276 277 278
      # error number.
      def error_number(exception) # :nodoc:
        raise NotImplementedError
      end

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

279 280 281
      def _quote(value) # :nodoc:
        if value.is_a?(Type::Binary::Data)
          "x'#{value.hex}'"
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
        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

299 300 301 302
      def unquoted_true
        1
      end

303 304 305 306
      def quoted_false
        QUOTED_FALSE
      end

307 308 309 310
      def unquoted_false
        0
      end

311 312 313 314 315 316 317 318
      def quoted_date(value)
        if supports_datetime_with_precision?
          super
        else
          super.sub(/\.\d{6}\z/, '')
        end
      end

319 320
      # REFERENTIAL INTEGRITY ====================================

R
Rafael Mendonça França 已提交
321
      def disable_referential_integrity #:nodoc:
322 323 324 325 326 327 328 329 330 331
        old = select_value("SELECT @@FOREIGN_KEY_CHECKS")

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

332
      #--
333
      # DATABASE STATEMENTS ======================================
334
      #++
335

336 337 338 339 340 341 342 343 344 345
      def explain(arel, binds = [])
        sql     = "EXPLAIN #{to_sql(arel, binds)}"
        start   = Time.now
        result  = exec_query(sql, 'EXPLAIN', binds)
        elapsed = Time.now - start

        ExplainPrettyPrinter.new.pp(result, elapsed)
      end

      class ExplainPrettyPrinter # :nodoc:
346
        # Pretty prints the result of an EXPLAIN in a way that resembles the output of the
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
        # MySQL shell:
        #
        #   +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
        #   | id | select_type | table | type  | possible_keys | key     | key_len | ref   | rows | Extra       |
        #   +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
        #   |  1 | SIMPLE      | users | const | PRIMARY       | PRIMARY | 4       | const |    1 |             |
        #   |  1 | SIMPLE      | posts | ALL   | NULL          | NULL    | NULL    | NULL  |    1 | Using where |
        #   +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
        #   2 rows in set (0.00 sec)
        #
        # This is an exercise in Ruby hyperrealism :).
        def pp(result, elapsed)
          widths    = compute_column_widths(result)
          separator = build_separator(widths)

          pp = []

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

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

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

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

        private

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

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

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

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

410 411 412 413 414 415 416 417 418 419 420
      def select_all(arel, name = nil, binds = [])
        rows = if ExplainRegistry.collect? && prepared_statements
          unprepared_statement { super }
        else
          super
        end
        @connection.next_result while @connection.more_results?
        rows
      end

      # Clears the prepared statements cache.
421 422
      def clear_cache!
        reload_type_map
423
        @statements.clear
424 425
      end

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

      # MysqlAdapter has to free a result after using it, so we use this method to write
432
      # stuff in an abstract way without concerning ourselves about whether it needs to be
433
      # explicitly freed or not.
434
      def execute_and_free(sql, name = nil) # :nodoc:
435 436 437
        yield execute(sql, name)
      end

438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453
      def exec_delete(sql, name, binds) # :nodoc:
        if without_prepared_statement?(binds)
          execute_and_free(sql, name) { @connection.affected_rows }
        else
          exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows }
        end
      end
      alias :exec_update :exec_delete

      def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) # :nodoc:
        super
        id_value || last_inserted_id
      end
      alias :create :insert_sql

      def update_sql(sql, name = nil) # :nodoc:
454 455 456 457 458 459 460 461
        super
        @connection.affected_rows
      end

      def begin_db_transaction
        execute "BEGIN"
      end

462 463 464 465 466
      def begin_isolated_db_transaction(isolation)
        execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
        begin_db_transaction
      end

467 468 469 470
      def commit_db_transaction #:nodoc:
        execute "COMMIT"
      end

471
      def exec_rollback_db_transaction #:nodoc:
472 473 474 475 476
        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
477
      # these, we must use a subquery.
478 479
      def join_to_update(update, select) #:nodoc:
        if select.limit || select.offset || select.orders.any?
480
          super
481 482 483 484 485 486
        else
          update.table select.source
          update.wheres = select.constraints
        end
      end

J
Jon Leighton 已提交
487 488 489 490
      def empty_insert_statement_value
        "VALUES ()"
      end

491 492 493 494 495 496
      # 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)
497
        sql = create_database(name, options)
498
        reconnect!
499
        sql
500 501 502 503 504 505
      end

      # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
      # Charset defaults to utf8.
      #
      # Example:
A
AvnerCohen 已提交
506
      #   create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
507
      #   create_database 'matt_development'
A
AvnerCohen 已提交
508
      #   create_database 'matt_development', charset: :big5
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538
      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 已提交
539
      def tables(name = nil) # :nodoc:
540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555
        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 已提交
556
        sql = "SELECT table_name FROM information_schema.tables "
R
Ryuta Kamizono 已提交
557
        sql << "WHERE table_schema = #{quote(@config[:database])}"
558

R
Ryuta Kamizono 已提交
559
        select_values(sql, 'SCHEMA')
560 561
      end

562 563 564 565
      def truncate(table_name, name = nil)
        execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
      end

566
      def table_exists?(table_name)
567 568 569 570 571 572 573 574 575 576
        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)
577
        return false unless table_name.present?
578

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

582 583
        sql = "SELECT table_name FROM information_schema.tables "
        sql << "WHERE table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
584

585
        select_values(sql, 'SCHEMA').any?
586 587
      end

588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603
      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

604 605 606 607 608 609 610 611 612
      # 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]
613 614 615 616 617

              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)
618 619 620 621 622 623 624 625 626 627 628
            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+.
629
      def columns(table_name)#:nodoc:
630
        sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
631 632
        execute_and_free(sql, 'SCHEMA') do |result|
          each_hash(result).map do |field|
633 634
            type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
            new_column(field[:Field], field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
635 636 637 638 639
          end
        end
      end

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

643
      def bulk_change_table(table_name, operations) #:nodoc:
644
        sqls = operations.flat_map do |command, args|
645 646 647
          table, arguments = args.shift, args
          method = :"#{command}_sql"

648
          if respond_to?(method, true)
649 650 651 652
            send(method, table, *arguments)
          else
            raise "Unknown method called : #{method}(#{arguments.inspect})"
          end
653
        end.join(", ")
654 655 656 657

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

658 659 660 661 662 663
      # 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)}"
664
        rename_table_indexes(table_name, new_name)
665 666
      end

667 668 669 670 671 672
      # 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>]
673
      #   Set to +true+ to only drop the table if it exists.
674 675 676 677
      #   Defaults to false.
      # [<tt>:temporary</tt>]
      #   Set to +true+ to drop temporary table.
      #   Defaults to false.
678 679 680 681
      #
      # 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.
682
      def drop_table(table_name, options = {})
683
        execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
684 685
      end

686
      def rename_index(table_name, old_name, new_name)
687
        if supports_rename_index?
688 689
          validate_index_length!(table_name, new_name)

690 691 692 693 694 695
          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

696 697
      def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
        default = extract_new_default_value(default_or_changes)
698 699 700 701
        column = column_for(table_name, column_name)
        change_column table_name, column_name, column.sql_type, :default => default
      end

702
      def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
703 704 705 706 707 708 709 710 711 712 713 714 715 716 717
        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)}")
718
        rename_column_indexes(table_name, column_name, new_column_name)
719 720
      end

D
doabit 已提交
721
      def add_index(table_name, column_name, options = {}) #:nodoc:
722 723
        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 已提交
724 725
      end

726
      def foreign_keys(table_name)
727
        fk_info = select_all <<-SQL.strip_heredoc
728 729 730 731 732 733 734 735
          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}'
736
        SQL
737

738
        create_table_info = create_table_info(table_name)
739 740

        fk_info.map do |row|
741 742 743 744 745 746
          options = {
            column: row['column'],
            name: row['name'],
            primary_key: row['primary_key']
          }

Y
Yves Senn 已提交
747 748
          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")
749

750 751 752 753
          ForeignKeyDefinition.new(table_name, row['to_table'], options)
        end
      end

754
      def table_options(table_name)
755
        create_table_info = create_table_info(table_name)
756 757 758 759 760 761 762 763

        # 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

764
      # Maps logical Rails types to MySQL-specific data types.
765 766
      def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil)
        sql = case type.to_s
767
        when 'integer'
768
          integer_to_sql(limit)
769
        when 'text'
770
          text_to_sql(limit)
771 772 773 774 775 776 777 778
        when 'blob'
          binary_to_sql(limit)
        when 'binary'
          if (0..0xfff) === limit
            "varbinary(#{limit})"
          else
            binary_to_sql(limit)
          end
779
        else
780
          super(type, limit, precision, scale)
781
        end
782 783 784

        sql << ' unsigned' if unsigned && type != :primary_key
        sql
785 786 787 788
      end

      # SHOW VARIABLES LIKE 'name'
      def show_variable(name)
789 790 791 792
        variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
        variables.first['Value'] unless variables.empty?
      rescue ActiveRecord::StatementInvalid
        nil
793 794
      end

795 796 797 798 799 800 801 802 803 804 805 806 807 808
      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
809 810
      end

811 812
      def case_sensitive_modifier(node, table_attribute)
        node = Arel::Nodes.build_quoted node, table_attribute
813 814 815
        Arel::Nodes::Bin.new(node)
      end

816 817 818 819 820 821 822 823
      def case_sensitive_comparison(table, attribute, column, value)
        if column.case_sensitive?
          table[attribute].eq(value)
        else
          super
        end
      end

824 825 826 827 828 829 830 831
      def case_insensitive_comparison(table, attribute, column, value)
        if column.case_sensitive?
          super
        else
          table[attribute].eq(value)
        end
      end

832
      def strict_mode?
833
        self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
834 835
      end

836 837 838 839
      def valid_type?(type)
        !native_database_types[type].nil?
      end

840 841
      protected

S
Sean Griffin 已提交
842
      def initialize_type_map(m) # :nodoc:
843
        super
844

845 846
        register_class_with_limit m, %r(char)i, MysqlString

847 848 849 850 851 852
        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)
853 854
        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 已提交
855 856
        m.register_type %r(^float)i,     Type::Float.new(limit: 24)
        m.register_type %r(^double)i,    Type::Float.new(limit: 53)
857
        m.register_type %r(^json)i,      MysqlJson.new
S
Sean Griffin 已提交
858

859 860 861 862 863 864
        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

865 866 867
        m.alias_type %r(tinyint\(1\))i,  'boolean' if emulate_booleans
        m.alias_type %r(year)i,          'integer'
        m.alias_type %r(bit)i,           'binary'
868 869 870 871

        m.register_type(%r(enum)i) do |sql_type|
          limit = sql_type[/^enum\((.+)\)/i, 1]
            .split(',').map{|enum| enum.strip.length - 2}.max
872
          MysqlString.new(limit: limit)
873
        end
874 875 876 877 878 879

        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
880 881
      end

882 883 884 885 886 887 888 889 890 891
      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

892
      def extract_precision(sql_type)
893
        if /time/ === sql_type
894 895 896 897 898 899
          super || 0
        else
          super
        end
      end

900 901
      def fetch_type_metadata(sql_type, extra = "")
        MysqlTypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
S
Sean Griffin 已提交
902 903
      end

904 905 906 907
      def add_index_length(option_strings, column_names, options = {})
        if options.is_a?(Hash) && length = options[:length]
          case length
          when Hash
908
            column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
909 910 911 912 913 914 915 916
          when Fixnum
            column_names.each {|name| option_strings[name] += "(#{length})"}
          end
        end

        return option_strings
      end

917
      def quoted_columns_for_index(column_names, options = {})
918
        option_strings = Hash[column_names.map {|name| [name, '']}]
919

920 921 922 923 924 925 926
        # 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]}
927 928 929 930 931
      end

      def translate_exception(exception, message)
        case error_number(exception)
        when 1062
932
          RecordNotUnique.new(message)
933
        when 1452
934
          InvalidForeignKey.new(message)
935 936 937 938 939 940
        else
          super
        end
      end

      def add_column_sql(table_name, column_name, type, options = {})
941
        td = create_table_definition(table_name)
942
        cd = td.new_column_definition(column_name, type, options)
943
        schema_creation.accept(AddColumnDefinition.new(cd))
944 945 946 947 948 949 950 951 952 953 954 955 956
      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

957 958 959
        td = create_table_definition(table_name)
        cd = td.new_column_definition(column.name, type, options)
        schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
960 961 962
      end

      def rename_column_sql(table_name, column_name, new_column_name)
963
        column  = column_for(table_name, column_name)
964 965 966
        options = {
          default: column.default,
          null: column.null,
967
          auto_increment: column.auto_increment?
968
        }
969

K
kennyj 已提交
970
        current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
971 972 973
        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))
974 975
      end

M
Marc-Andre Lafortune 已提交
976 977 978 979 980 981
      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) }
982 983 984
      end

      def add_index_sql(table_name, column_name, options = {})
985 986 987
        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}"
988 989 990 991 992 993 994
      end

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

995 996
      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)]
997 998
      end

999
      def remove_timestamps_sql(table_name, options = {})
1000 1001 1002
        [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
      end

1003 1004
      private

1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017
      # 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

1018
      def mariadb?
1019
        full_version =~ /mariadb/i
1020 1021 1022
      end

      def supports_rename_index?
1023
        mariadb? ? false : version >= '5.7.6'
1024 1025
      end

1026
      def configure_connection
1027
        variables = @config.fetch(:variables, {}).stringify_keys
1028

1029
        # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
1030
        variables['sql_auto_is_null'] = 0
1031 1032 1033 1034

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

1037 1038
        defaults = [':default', :default].to_set

1039
        # Make MySQL reject illegal values rather than truncating or blanking them, see
1040
        # http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
1041
        # If the user has provided another value for sql_mode, don't replace it.
1042
        unless variables.has_key?('sql_mode') || defaults.include?(@config[:strict])
1043
          variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
1044 1045 1046
        end

        # NAMES does not have an equals sign, see
1047
        # http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430
1048
        # (trailing comma because variable_assignments will always have content)
1049 1050 1051 1052 1053
        if @config[:encoding]
          encoding = "NAMES #{@config[:encoding]}"
          encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
          encoding << ", "
        end
1054 1055 1056

        # Gather up all of the SET variables...
        variable_assignments = variables.map do |k, v|
1057
          if defaults.include?(v)
1058
            "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
1059
          elsif !v.nil?
1060
            "@@SESSION.#{k} = #{quote(v)}"
1061 1062 1063 1064 1065
          end
          # or else nil; compact to clear nils out
        end.compact.join(', ')

        # ...and send them all in one query
1066
        @connection.query  "SET #{encoding} #{variable_assignments}"
1067
      end
1068 1069 1070 1071 1072 1073 1074 1075 1076

      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
1077

1078 1079 1080 1081 1082
      def create_table_info(table_name) # :nodoc:
        @create_table_info_cache = {}
        @create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
      end

1083
      def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
1084
        MySQL::TableDefinition.new(name, temporary, options, as)
1085 1086
      end

1087 1088 1089 1090 1091
      def integer_to_sql(limit) # :nodoc:
        case limit
        when 1; 'tinyint'
        when 2; 'smallint'
        when 3; 'mediumint'
1092
        when nil, 4; 'int'
1093
        when 5..8; 'bigint'
1094
        when 11; 'int(11)' # backward compatibility with Rails 2.0
1095 1096 1097 1098 1099 1100 1101 1102 1103 1104
        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'
1105
        else raise(ActiveRecordError, "No text type has byte length #{limit}")
1106 1107 1108
        end
      end

1109 1110 1111 1112 1113 1114 1115 1116 1117 1118
      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

1119
      class MysqlJson < Type::Internal::AbstractJson # :nodoc:
1120 1121 1122 1123 1124 1125 1126
        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

1127
      class MysqlString < Type::String # :nodoc:
1128
        def serialize(value)
1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145
          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
1146

1147 1148
      ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql)
      ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2)
1149 1150
      ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql)
      ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
1151 1152
      ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql)
      ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
1153 1154 1155
    end
  end
end