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

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

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

        def json(*args, **options)
          args.each { |name| column(name, :json, options) }
        end
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

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

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

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

        def unsigned_decimal(*args, **options)
          args.each { |name| column(name, :unsigned_decimal, options) }
        end
33 34
      end

35
      class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
36
        attr_accessor :charset, :unsigned
37 38
      end

39 40
      class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
        include ColumnMethods
41 42 43 44 45 46 47

        def new_column_definition(name, type, options) # :nodoc:
          column = super
          case column.type
          when :primary_key
            column.type = :integer
            column.auto_increment = true
48 49 50
          when /\Aunsigned_(?<type>.+)\z/
            column.type = $~[:type].to_sym
            column.unsigned = true
51
          end
52
          column.unsigned ||= options[:unsigned]
53
          column.charset = options[:charset]
54 55
          column
        end
56 57 58 59 60 61

        private

        def create_column_definition(name, type)
          ColumnDefinition.new(name, type)
        end
62 63
      end

64 65 66 67
      class Table < ActiveRecord::ConnectionAdapters::Table
        include ColumnMethods
      end

68
      class SchemaCreation < AbstractAdapter::SchemaCreation
69 70
        private

71 72 73 74
        def visit_DropForeignKey(name)
          "DROP FOREIGN KEY #{name}"
        end

75 76 77 78 79
        def visit_ColumnDefinition(o)
          o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.unsigned)
          super
        end

80 81 82 83
        def visit_AddColumnDefinition(o)
          add_column_position!(super, column_options(o.column))
        end

84
        def visit_ChangeColumnDefinition(o)
85 86
          change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}"
          add_column_position!(change_column_sql, column_options(o.column))
87 88
        end

89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
        def column_options(o)
          column_options = super
          column_options[:charset] = o.charset
          column_options
        end

        def add_column_options!(sql, options)
          if options[:charset]
            sql << " CHARACTER SET #{options[:charset]}"
          end
          if options[:collation]
            sql << " COLLATE #{options[:collation]}"
          end
          super
        end

105 106
        def add_column_position!(sql, options)
          if options[:first]
107
            sql << " FIRST"
108 109
          elsif options[:after]
            sql << " AFTER #{quote_column_name(options[:after])}"
110 111 112
          end
          sql
        end
113 114

        def index_in_create(table_name, column_name, options)
115 116
          index_name, index_type, index_columns, _, _, index_using = @conn.add_index_options(table_name, column_name, options)
          "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns}) "
117
        end
118 119
      end

120 121 122 123
      def update_table_definition(table_name, base) # :nodoc:
        Table.new(table_name, base)
      end

124 125 126 127
      def schema_creation
        SchemaCreation.new self
      end

128 129
      def column_spec_for_primary_key(column)
        spec = {}
130
        if column.auto_increment?
R
Ryuta Kamizono 已提交
131
          spec[:id] = ':bigint' if column.bigint?
132
          spec[:unsigned] = 'true' if column.unsigned?
R
Ryuta Kamizono 已提交
133
          return if spec.empty?
134 135 136 137 138 139 140
        else
          spec[:id] = column.type.inspect
          spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) })
        end
        spec
      end

141 142 143 144 145 146 147 148 149 150
      def prepare_column_options(column)
        spec = super
        spec[:unsigned] = 'true' if column.unsigned?
        spec
      end

      def migration_keys
        super + [:unsigned]
      end

151 152 153 154 155 156 157 158
      private

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

      def schema_precision(column)
        super unless /time/ === column.sql_type && column.precision == 0
159 160 161
      end

      def schema_collation(column)
162 163 164
        if column.collation && table_name = column.instance_variable_get(:@table_name)
          @collation_cache ||= {}
          @collation_cache[table_name] ||= select_one("SHOW TABLE STATUS LIKE '#{table_name}'")["Collation"]
165
          column.collation.inspect if column.collation != @collation_cache[table_name]
166 167
        end
      end
168 169

      public
170

171
      class Column < ConnectionAdapters::Column # :nodoc:
172
        delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true
173

S
Sean Griffin 已提交
174 175
        def initialize(*)
          super
176
          assert_valid_default(default)
177
          extract_default
178 179
        end

180 181 182
        def extract_default
          if blob_or_text_column?
            @default = null || strict ? nil : ''
S
Sean Griffin 已提交
183
          elsif missing_default_forged_as_empty_string?(default)
184
            @default = nil
185 186 187 188
          end
        end

        def has_default?
189
          return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
190 191
          super
        end
192

193 194 195
        def blob_or_text_column?
          sql_type =~ /blob/i || type == :text
        end
196

197 198 199 200
        def unsigned?
          /unsigned/ === sql_type
        end

201 202 203 204
        def case_sensitive?
          collation && !collation.match(/_ci$/)
        end

205 206 207 208
        def auto_increment?
          extra == 'auto_increment'
        end

209 210 211 212 213 214 215 216 217 218 219 220
        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
221 222 223 224 225 226

        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 已提交
227 228 229
      end

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

232
        def initialize(type_metadata, extra: "", strict: false)
S
Sean Griffin 已提交
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
          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
250 251

        def attributes_for_hash
252
          [self.class, @type_metadata, extra, strict]
253
        end
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
      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 = {
276
        primary_key: "int auto_increment PRIMARY KEY",
277 278 279 280 281 282 283 284 285 286 287 288
        string:      { name: "varchar", limit: 255 },
        text:        { name: "text" },
        integer:     { name: "int", limit: 4 },
        float:       { name: "float" },
        decimal:     { name: "decimal" },
        datetime:    { name: "datetime" },
        time:        { name: "time" },
        date:        { name: "date" },
        binary:      { name: "blob" },
        boolean:     { name: "tinyint", limit: 1 },
        bigint:      { name: "bigint" },
        json:        { name: "json" },
289 290
      }

291 292 293
      INDEX_TYPES  = [:fulltext, :spatial]
      INDEX_USINGS = [:btree, :hash]

294 295 296 297 298
      # 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 = {}, {}
299

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

302
        if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
303
          @prepared_statements = true
304
        else
A
Aaron Patterson 已提交
305 306 307 308
          @prepared_statements = false
        end
      end

309 310 311 312 313 314 315 316 317 318
      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

319 320 321 322 323 324 325 326 327
      # Returns true, since this connection adapter supports migrations.
      def supports_migrations?
        true
      end

      def supports_primary_key?
        true
      end

328 329 330 331
      def supports_bulk_alter? #:nodoc:
        true
      end

332
      # Technically MySQL allows to create indexes with the sort order syntax
333 334 335 336 337
      # but at the moment (5.5) it doesn't yet implement them
      def supports_index_sort_order?
        true
      end

338 339 340 341 342
      # 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?
343
        version >= '5.0.0'
344 345
      end

346 347 348 349
      def supports_explain?
        true
      end

350 351 352 353
      def supports_indexes_in_create?
        true
      end

354 355 356 357
      def supports_foreign_keys?
        true
      end

358
      def supports_views?
359
        version >= '5.0.0'
360 361
      end

362
      def supports_datetime_with_precision?
363
        version >= '5.6.4'
364 365
      end

366 367 368 369
      def native_database_types
        NATIVE_DATABASE_TYPES
      end

370 371 372 373
      def index_algorithms
        { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
      end

374 375 376 377 378 379 380 381
      # 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

382 383
      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)
384 385
      end

386
      # Must return the MySQL error number from the exception, if the exception has an
387 388 389 390 391 392 393
      # error number.
      def error_number(exception) # :nodoc:
        raise NotImplementedError
      end

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

394 395 396
      def _quote(value) # :nodoc:
        if value.is_a?(Type::Binary::Data)
          "x'#{value.hex}'"
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413
        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

414 415 416 417
      def unquoted_true
        1
      end

418 419 420 421
      def quoted_false
        QUOTED_FALSE
      end

422 423 424 425
      def unquoted_false
        0
      end

426 427 428 429 430 431 432 433
      def quoted_date(value)
        if supports_datetime_with_precision?
          super
        else
          super.sub(/\.\d{6}\z/, '')
        end
      end

434 435
      # REFERENTIAL INTEGRITY ====================================

R
Rafael Mendonça França 已提交
436
      def disable_referential_integrity #:nodoc:
437 438 439 440 441 442 443 444 445 446
        old = select_value("SELECT @@FOREIGN_KEY_CHECKS")

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

447
      #--
448
      # DATABASE STATEMENTS ======================================
449
      #++
450

451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
      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:
        # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
        # 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

525 526 527 528 529
      def clear_cache!
        super
        reload_type_map
      end

530 531
      # Executes the SQL statement in the context of this connection.
      def execute(sql, name = nil)
532
        log(sql, name) { @connection.query(sql) }
533 534 535
      end

      # MysqlAdapter has to free a result after using it, so we use this method to write
536
      # stuff in an abstract way without concerning ourselves about whether it needs to be
537 538 539 540 541 542 543 544 545 546 547 548 549 550
      # explicitly freed or not.
      def execute_and_free(sql, name = nil) #:nodoc:
        yield execute(sql, name)
      end

      def update_sql(sql, name = nil) #:nodoc:
        super
        @connection.affected_rows
      end

      def begin_db_transaction
        execute "BEGIN"
      end

551 552 553 554 555
      def begin_isolated_db_transaction(isolation)
        execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
        begin_db_transaction
      end

556 557 558 559
      def commit_db_transaction #:nodoc:
        execute "COMMIT"
      end

560
      def exec_rollback_db_transaction #:nodoc:
561 562 563 564 565
        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
566
      # these, we must use a subquery.
567 568
      def join_to_update(update, select) #:nodoc:
        if select.limit || select.offset || select.orders.any?
569
          super
570 571 572 573 574 575
        else
          update.table select.source
          update.wheres = select.constraints
        end
      end

J
Jon Leighton 已提交
576 577 578 579
      def empty_insert_statement_value
        "VALUES ()"
      end

580 581 582 583 584 585
      # 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)
586
        sql = create_database(name, options)
587
        reconnect!
588
        sql
589 590 591 592 593 594
      end

      # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
      # Charset defaults to utf8.
      #
      # Example:
A
AvnerCohen 已提交
595
      #   create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
596
      #   create_database 'matt_development'
A
AvnerCohen 已提交
597
      #   create_database 'matt_development', charset: :big5
598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627
      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

628 629
      def tables(name = nil) # :nodoc:
        select_values("SHOW FULL TABLES", 'SCHEMA')
630
      end
631
      alias data_sources tables
632

633 634 635 636
      def truncate(table_name, name = nil)
        execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
      end

637 638
      def table_exists?(table_name)
        return false unless table_name.present?
639

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

643 644
        sql = "SELECT table_name FROM information_schema.tables "
        sql << "WHERE table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
645

646
        select_values(sql, 'SCHEMA').any?
647
      end
648
      alias data_source_exists? table_exists?
649

650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665
      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

666 667 668 669 670 671 672 673 674
      # 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]
675 676 677 678 679

              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)
680 681 682 683 684 685 686 687 688 689 690
            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+.
691
      def columns(table_name)#:nodoc:
692
        sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
693 694
        execute_and_free(sql, 'SCHEMA') do |result|
          each_hash(result).map do |field|
695
            field_name = set_field_encoding(field[:Field])
696
            sql_type = field[:Type]
697 698
            type_metadata = fetch_type_metadata(sql_type, field[:Extra])
            new_column(field_name, field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
699 700 701 702 703 704 705 706
          end
        end
      end

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

707
      def bulk_change_table(table_name, operations) #:nodoc:
708
        sqls = operations.flat_map do |command, args|
709 710 711
          table, arguments = args.shift, args
          method = :"#{command}_sql"

712
          if respond_to?(method, true)
713 714 715 716
            send(method, table, *arguments)
          else
            raise "Unknown method called : #{method}(#{arguments.inspect})"
          end
717
        end.join(", ")
718 719 720 721

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

722 723 724 725 726 727
      # 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)}"
728
        rename_table_indexes(table_name, new_name)
729 730
      end

731 732 733 734 735 736
      # 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>]
737
      #   Set to +true+ to only drop the table if it exists.
738 739 740 741
      #   Defaults to false.
      # [<tt>:temporary</tt>]
      #   Set to +true+ to drop temporary table.
      #   Defaults to false.
742 743 744 745
      #
      # 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.
746
      def drop_table(table_name, options = {})
747
        execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
748 749
      end

750
      def rename_index(table_name, old_name, new_name)
751
        if supports_rename_index?
752 753
          validate_index_length!(table_name, new_name)

754 755 756 757 758 759
          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

760 761
      def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
        default = extract_new_default_value(default_or_changes)
762 763 764 765
        column = column_for(table_name, column_name)
        change_column table_name, column_name, column.sql_type, :default => default
      end

766
      def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
767 768 769 770 771 772 773 774 775 776 777 778 779 780 781
        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)}")
782
        rename_column_indexes(table_name, column_name, new_column_name)
783 784
      end

D
doabit 已提交
785
      def add_index(table_name, column_name, options = {}) #:nodoc:
786 787
        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 已提交
788 789
      end

790
      def foreign_keys(table_name)
791
        fk_info = select_all <<-SQL.strip_heredoc
792 793 794 795 796 797 798 799
          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}'
800
        SQL
801

802
        create_table_info = create_table_info(table_name)
803 804

        fk_info.map do |row|
805 806 807 808 809 810
          options = {
            column: row['column'],
            name: row['name'],
            primary_key: row['primary_key']
          }

Y
Yves Senn 已提交
811 812
          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")
813

814 815 816 817
          ForeignKeyDefinition.new(table_name, row['to_table'], options)
        end
      end

818
      def table_options(table_name)
819
        create_table_info = create_table_info(table_name)
820 821 822 823 824 825 826 827

        # 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

828
      # Maps logical Rails types to MySQL-specific data types.
829 830
      def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil)
        sql = case type.to_s
831
        when 'binary'
832
          binary_to_sql(limit)
833
        when 'integer'
834
          integer_to_sql(limit)
835
        when 'text'
836
          text_to_sql(limit)
837
        else
838
          super(type, limit, precision, scale)
839
        end
840 841 842

        sql << ' unsigned' if unsigned && type != :primary_key
        sql
843 844 845 846
      end

      # SHOW VARIABLES LIKE 'name'
      def show_variable(name)
847 848 849 850
        variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
        variables.first['Value'] unless variables.empty?
      rescue ActiveRecord::StatementInvalid
        nil
851 852 853 854
      end

      # Returns a table's primary key and belonging sequence.
      def pk_and_sequence_for(table)
855 856
        if pk = primary_key(table)
          [ pk, nil ]
857 858 859
        end
      end

860 861 862 863 864 865 866 867 868 869 870 871 872 873
      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
874 875
      end

876 877
      def case_sensitive_modifier(node, table_attribute)
        node = Arel::Nodes.build_quoted node, table_attribute
878 879 880
        Arel::Nodes::Bin.new(node)
      end

881 882 883 884 885 886 887 888
      def case_sensitive_comparison(table, attribute, column, value)
        if column.case_sensitive?
          table[attribute].eq(value)
        else
          super
        end
      end

889 890 891 892 893 894 895 896
      def case_insensitive_comparison(table, attribute, column, value)
        if column.case_sensitive?
          super
        else
          table[attribute].eq(value)
        end
      end

897
      def strict_mode?
898
        self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
899 900
      end

901 902 903 904
      def valid_type?(type)
        !native_database_types[type].nil?
      end

905 906
      protected

S
Sean Griffin 已提交
907
      def initialize_type_map(m) # :nodoc:
908
        super
909

910 911
        register_class_with_limit m, %r(char)i, MysqlString

912 913 914 915 916 917
        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)
918 919
        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 已提交
920 921
        m.register_type %r(^float)i,     Type::Float.new(limit: 24)
        m.register_type %r(^double)i,    Type::Float.new(limit: 53)
922
        m.register_type %r(^json)i,      MysqlJson.new
S
Sean Griffin 已提交
923

924 925 926 927 928 929
        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

930 931 932
        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'
933 934 935 936

        m.register_type(%r(enum)i) do |sql_type|
          limit = sql_type[/^enum\((.+)\)/i, 1]
            .split(',').map{|enum| enum.strip.length - 2}.max
937
          MysqlString.new(limit: limit)
938
        end
939 940 941 942 943 944

        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
945 946
      end

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

957
      def extract_precision(sql_type)
958
        if /time/ === sql_type
959 960 961 962 963 964
          super || 0
        else
          super
        end
      end

965 966
      def fetch_type_metadata(sql_type, extra = "")
        MysqlTypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
S
Sean Griffin 已提交
967 968
      end

969 970 971 972
      def add_index_length(option_strings, column_names, options = {})
        if options.is_a?(Hash) && length = options[:length]
          case length
          when Hash
973
            column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
974 975 976 977 978 979 980 981
          when Fixnum
            column_names.each {|name| option_strings[name] += "(#{length})"}
          end
        end

        return option_strings
      end

982
      def quoted_columns_for_index(column_names, options = {})
983
        option_strings = Hash[column_names.map {|name| [name, '']}]
984

985 986 987 988 989 990 991
        # 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]}
992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005
      end

      def translate_exception(exception, message)
        case error_number(exception)
        when 1062
          RecordNotUnique.new(message, exception)
        when 1452
          InvalidForeignKey.new(message, exception)
        else
          super
        end
      end

      def add_column_sql(table_name, column_name, type, options = {})
1006
        td = create_table_definition(table_name)
1007
        cd = td.new_column_definition(column_name, type, options)
1008
        schema_creation.accept(AddColumnDefinition.new(cd))
1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021
      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

1022 1023 1024
        td = create_table_definition(table_name)
        cd = td.new_column_definition(column.name, type, options)
        schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
1025 1026 1027
      end

      def rename_column_sql(table_name, column_name, new_column_name)
1028
        column  = column_for(table_name, column_name)
1029 1030 1031
        options = {
          default: column.default,
          null: column.null,
1032
          auto_increment: column.auto_increment?
1033
        }
1034

K
kennyj 已提交
1035
        current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
1036 1037 1038
        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))
1039 1040
      end

M
Marc-Andre Lafortune 已提交
1041 1042 1043 1044 1045 1046
      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) }
1047 1048 1049
      end

      def add_index_sql(table_name, column_name, options = {})
1050 1051 1052
        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}"
1053 1054 1055 1056 1057 1058 1059
      end

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

1060 1061
      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)]
1062 1063
      end

1064
      def remove_timestamps_sql(table_name, options = {})
1065 1066 1067
        [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
      end

1068 1069
      private

1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082
      # 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

1083
      def version
1084
        @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
1085 1086 1087
      end

      def mariadb?
1088
        full_version =~ /mariadb/i
1089 1090 1091
      end

      def supports_rename_index?
1092
        mariadb? ? false : version >= '5.7.6'
1093 1094
      end

1095
      def configure_connection
1096
        variables = @config.fetch(:variables, {}).stringify_keys
1097

1098
        # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
1099
        variables['sql_auto_is_null'] = 0
1100 1101 1102 1103

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

1106 1107
        defaults = [':default', :default].to_set

1108
        # Make MySQL reject illegal values rather than truncating or blanking them, see
1109
        # http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
1110
        # If the user has provided another value for sql_mode, don't replace it.
1111
        unless variables.has_key?('sql_mode') || defaults.include?(@config[:strict])
1112
          variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
1113 1114 1115
        end

        # NAMES does not have an equals sign, see
1116
        # http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430
1117
        # (trailing comma because variable_assignments will always have content)
1118 1119 1120 1121 1122
        if @config[:encoding]
          encoding = "NAMES #{@config[:encoding]}"
          encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
          encoding << ", "
        end
1123 1124 1125

        # Gather up all of the SET variables...
        variable_assignments = variables.map do |k, v|
1126
          if defaults.include?(v)
1127
            "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
1128
          elsif !v.nil?
1129
            "@@SESSION.#{k} = #{quote(v)}"
1130 1131 1132 1133 1134
          end
          # or else nil; compact to clear nils out
        end.compact.join(', ')

        # ...and send them all in one query
1135
        @connection.query  "SET #{encoding} #{variable_assignments}"
1136
      end
1137 1138 1139 1140 1141 1142 1143 1144 1145

      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
1146

1147 1148 1149 1150 1151
      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

1152
      def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
1153 1154 1155
        TableDefinition.new(native_database_types, name, temporary, options, as)
      end

1156 1157 1158 1159 1160
      def binary_to_sql(limit) # :nodoc:
        case limit
        when 0..0xfff;           "varbinary(#{limit})"
        when nil;                "blob"
        when 0x1000..0xffffffff; "blob(#{limit})"
1161
        else raise(ActiveRecordError, "No binary type has byte length #{limit}")
1162 1163 1164 1165 1166 1167 1168 1169
        end
      end

      def integer_to_sql(limit) # :nodoc:
        case limit
        when 1; 'tinyint'
        when 2; 'smallint'
        when 3; 'mediumint'
1170
        when nil, 4; 'int'
1171
        when 5..8; 'bigint'
1172
        when 11; 'int(11)' # backward compatibility with Rails 2.0
1173 1174 1175 1176 1177 1178 1179 1180 1181 1182
        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'
1183
        else raise(ActiveRecordError, "No text type has byte length #{limit}")
1184 1185 1186
        end
      end

1187
      class MysqlJson < Type::Internal::AbstractJson # :nodoc:
1188 1189 1190 1191 1192 1193 1194
        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

1195
      class MysqlString < Type::String # :nodoc:
1196
        def serialize(value)
1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213
          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
1214

1215 1216
      ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql)
      ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2)
1217 1218
      ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql)
      ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
1219 1220
      ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql)
      ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
1221 1222 1223
    end
  end
end