abstract_mysql_adapter.rb 32.6 KB
Newer Older
1
require 'arel/visitors/bind_visitor'
2
require 'active_support/core_ext/string/strip'
3 4 5 6

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

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

      class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
        include ColumnMethods
18 19 20 21 22 23 24 25 26 27

        def new_column_definition(name, type, options) # :nodoc:
          column = super
          case column.type
          when :primary_key
            column.type = :integer
            column.auto_increment = true
          end
          column
        end
28 29
      end

30 31 32 33
      class Table < ActiveRecord::ConnectionAdapters::Table
        include ColumnMethods
      end

34
      class SchemaCreation < AbstractAdapter::SchemaCreation
R
Rafael Mendonça França 已提交
35 36 37 38
        def visit_AddColumn(o)
          add_column_position!(super, column_options(o))
        end

39 40
        private

41 42 43 44
        def visit_DropForeignKey(name)
          "DROP FOREIGN KEY #{name}"
        end

45
        def visit_TableDefinition(o)
R
Rafael Mendonça França 已提交
46 47 48 49 50 51
          name = o.name
          create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} "

          statements = o.columns.map { |c| accept c }
          statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) })

52
          create_sql << "(#{statements.join(', ')}) " if statements.present?
53 54 55 56
          create_sql << "#{o.options}"
          create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
          create_sql
        end
57

58
        def visit_ChangeColumnDefinition(o)
59 60
          change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}"
          add_column_position!(change_column_sql, column_options(o.column))
61 62
        end

63 64
        def add_column_position!(sql, options)
          if options[:first]
65
            sql << " FIRST"
66 67
          elsif options[:after]
            sql << " AFTER #{quote_column_name(options[:after])}"
68 69 70
          end
          sql
        end
71 72

        def index_in_create(table_name, column_name, options)
73
          index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
74
          "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
75
        end
76 77
      end

78 79 80 81
      def update_table_definition(table_name, base) # :nodoc:
        Table.new(table_name, base)
      end

82 83 84 85
      def schema_creation
        SchemaCreation.new self
      end

86 87
      def column_spec_for_primary_key(column)
        spec = {}
88
        if column.auto_increment?
89 90 91 92 93 94 95 96 97
          return unless column.limit == 8
          spec[:id] = ':bigint'
        else
          spec[:id] = column.type.inspect
          spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) })
        end
        spec
      end

98 99
      def prepare_column_options(column)
        spec = super
100
        spec.delete(:precision) if /time/ === column.sql_type && column.precision == 0
101
        spec.delete(:limit)     if :boolean === column.type
102 103 104
        spec
      end

105
      class Column < ConnectionAdapters::Column # :nodoc:
S
Sean Griffin 已提交
106
        delegate :strict, :collation, :extra, to: :sql_type_metadata, allow_nil: true
107

S
Sean Griffin 已提交
108 109
        def initialize(*)
          super
110
          assert_valid_default(default)
111
          extract_default
112 113
        end

114 115 116
        def extract_default
          if blob_or_text_column?
            @default = null || strict ? nil : ''
S
Sean Griffin 已提交
117
          elsif missing_default_forged_as_empty_string?(default)
118
            @default = nil
119 120 121 122
          end
        end

        def has_default?
123
          return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
124 125
          super
        end
126

127 128 129
        def blob_or_text_column?
          sql_type =~ /blob/i || type == :text
        end
130

131 132 133 134
        def case_sensitive?
          collation && !collation.match(/_ci$/)
        end

135 136 137 138
        def auto_increment?
          extra == 'auto_increment'
        end

139 140 141 142 143 144 145 146 147 148 149 150
        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
151 152 153 154 155 156

        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 已提交
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
      end

      class MysqlTypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
        attr_reader :collation, :extra, :strict

        def initialize(type_metadata, collation: "", extra: "", strict: false)
          super(type_metadata)
          @type_metadata = type_metadata
          @collation = collation
          @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
181 182

        def attributes_for_hash
S
Sean Griffin 已提交
183
          [self.class, @type_metadata, collation, extra, strict]
184
        end
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
      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 = {
207
        :primary_key => "int(11) auto_increment PRIMARY KEY",
208 209 210 211 212 213 214 215 216 217 218 219
        :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 }
      }

220 221 222
      INDEX_TYPES  = [:fulltext, :spatial]
      INDEX_USINGS = [:btree, :hash]

223 224 225 226 227
      # 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 = {}, {}
228

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

231
        if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
232
          @prepared_statements = true
233
        else
A
Aaron Patterson 已提交
234 235 236 237
          @prepared_statements = false
        end
      end

238 239 240 241 242 243 244 245 246 247
      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

248 249 250 251 252 253 254 255 256
      # Returns true, since this connection adapter supports migrations.
      def supports_migrations?
        true
      end

      def supports_primary_key?
        true
      end

257 258 259 260
      def supports_bulk_alter? #:nodoc:
        true
      end

261
      # Technically MySQL allows to create indexes with the sort order syntax
262 263 264 265 266
      # but at the moment (5.5) it doesn't yet implement them
      def supports_index_sort_order?
        true
      end

267 268 269 270 271 272 273 274
      # 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?
        version[0] >= 5
      end

275 276 277 278
      def supports_indexes_in_create?
        true
      end

279 280 281 282
      def supports_foreign_keys?
        true
      end

283 284 285 286
      def supports_views?
        version[0] >= 5
      end

287 288 289 290
      def supports_datetime_with_precision?
        (version[0] == 5 && version[1] >= 6) || version[0] >= 6
      end

291 292 293 294
      def native_database_types
        NATIVE_DATABASE_TYPES
      end

295 296 297 298
      def index_algorithms
        { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
      end

299 300 301 302 303 304 305 306
      # 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

S
Sean Griffin 已提交
307 308
      def new_column(field, default, sql_type_metadata = nil, null = true) # :nodoc:
        Column.new(field, default, sql_type_metadata, null)
309 310
      end

311
      # Must return the MySQL error number from the exception, if the exception has an
312 313 314 315 316 317 318
      # error number.
      def error_number(exception) # :nodoc:
        raise NotImplementedError
      end

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

319 320 321
      def _quote(value) # :nodoc:
        if value.is_a?(Type::Binary::Data)
          "x'#{value.hex}'"
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
        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

339 340 341 342
      def unquoted_true
        1
      end

343 344 345 346
      def quoted_false
        QUOTED_FALSE
      end

347 348 349 350
      def unquoted_false
        0
      end

351 352
      # REFERENTIAL INTEGRITY ====================================

R
Rafael Mendonça França 已提交
353
      def disable_referential_integrity #:nodoc:
354 355 356 357 358 359 360 361 362 363
        old = select_value("SELECT @@FOREIGN_KEY_CHECKS")

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

364
      #--
365
      # DATABASE STATEMENTS ======================================
366
      #++
367

368 369 370 371 372
      def clear_cache!
        super
        reload_type_map
      end

373 374
      # Executes the SQL statement in the context of this connection.
      def execute(sql, name = nil)
375
        log(sql, name) { @connection.query(sql) }
376 377 378
      end

      # MysqlAdapter has to free a result after using it, so we use this method to write
379
      # stuff in an abstract way without concerning ourselves about whether it needs to be
380 381 382 383 384 385 386 387 388 389 390 391 392 393
      # 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

394 395 396 397 398
      def begin_isolated_db_transaction(isolation)
        execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
        begin_db_transaction
      end

399 400 401 402
      def commit_db_transaction #:nodoc:
        execute "COMMIT"
      end

403
      def exec_rollback_db_transaction #:nodoc:
404 405 406 407 408
        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
409
      # these, we must use a subquery.
410 411
      def join_to_update(update, select) #:nodoc:
        if select.limit || select.offset || select.orders.any?
412
          super
413 414 415 416 417 418
        else
          update.table select.source
          update.wheres = select.constraints
        end
      end

J
Jon Leighton 已提交
419 420 421 422
      def empty_insert_statement_value
        "VALUES ()"
      end

423 424 425 426 427 428
      # 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)
429
        sql = create_database(name, options)
430
        reconnect!
431
        sql
432 433 434 435 436 437
      end

      # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
      # Charset defaults to utf8.
      #
      # Example:
A
AvnerCohen 已提交
438
      #   create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
439
      #   create_database 'matt_development'
A
AvnerCohen 已提交
440
      #   create_database 'matt_development', charset: :big5
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
      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

471 472
      def tables(name = nil, database = nil, like = nil) #:nodoc:
        sql = "SHOW TABLES "
473
        sql << "IN #{quote_table_name(database)} " if database
474
        sql << "LIKE #{quote(like)}" if like
475 476

        execute_and_free(sql, 'SCHEMA') do |result|
477
          result.collect(&:first)
478 479 480
        end
      end

481 482 483 484
      def truncate(table_name, name = nil)
        execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
      end

485
      def table_exists?(name)
486
        return false unless name.present?
487
        return true if tables(nil, nil, name).any?
488 489 490 491 492 493 494 495 496

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

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

497
        tables(nil, schema, table).any?
498 499 500 501 502 503 504 505 506 507 508
      end

      # Returns an array of indexes for the given table.
      def indexes(table_name, name = nil) #:nodoc:
        indexes = []
        current_index = nil
        execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
          each_hash(result) do |row|
            if current_index != row[:Key_name]
              next if row[:Key_name] == 'PRIMARY' # skip the primary key
              current_index = row[:Key_name]
509 510 511 512 513

              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)
514 515 516 517 518 519 520 521 522 523 524
            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+.
525
      def columns(table_name)#:nodoc:
526
        sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
527 528
        execute_and_free(sql, 'SCHEMA') do |result|
          each_hash(result).map do |field|
529
            field_name = set_field_encoding(field[:Field])
530
            sql_type = field[:Type]
S
Sean Griffin 已提交
531 532
            type_metadata = fetch_type_metadata(sql_type, field[:Collation], field[:Extra])
            new_column(field_name, field[:Default], type_metadata, field[:Null] == "YES")
533 534 535 536 537 538 539 540
          end
        end
      end

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

541
      def bulk_change_table(table_name, operations) #:nodoc:
542
        sqls = operations.flat_map do |command, args|
543 544 545
          table, arguments = args.shift, args
          method = :"#{command}_sql"

546
          if respond_to?(method, true)
547 548 549 550
            send(method, table, *arguments)
          else
            raise "Unknown method called : #{method}(#{arguments.inspect})"
          end
551
        end.join(", ")
552 553 554 555

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

556 557 558 559 560 561
      # 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)}"
562
        rename_table_indexes(table_name, new_name)
563 564
      end

565
      def drop_table(table_name, options = {})
566
        execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
567 568
      end

569
      def rename_index(table_name, old_name, new_name)
570
        if supports_rename_index?
571 572
          validate_index_length!(table_name, new_name)

573 574 575 576 577 578
          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

T
Tony Miller 已提交
579
      def change_column_default(table_name, column_name, default) #:nodoc:
580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599
        column = column_for(table_name, column_name)
        change_column table_name, column_name, column.sql_type, :default => default
      end

      def change_column_null(table_name, column_name, null, default = nil)
        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)}")
600
        rename_column_indexes(table_name, column_name, new_column_name)
601 602
      end

D
doabit 已提交
603
      def add_index(table_name, column_name, options = {}) #:nodoc:
604 605
        index_name, index_type, index_columns, index_options, 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_options} #{index_algorithm}"
D
doabit 已提交
606 607
      end

608
      def foreign_keys(table_name)
609
        fk_info = select_all <<-SQL.strip_heredoc
610 611 612 613 614 615 616 617
          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}'
618
        SQL
619 620 621 622

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

        fk_info.map do |row|
623 624 625 626 627 628
          options = {
            column: row['column'],
            name: row['name'],
            primary_key: row['primary_key']
          }

Y
Yves Senn 已提交
629 630
          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")
631

632 633 634 635
          ForeignKeyDefinition.new(table_name, row['to_table'], options)
        end
      end

636 637
      # Maps logical Rails types to MySQL-specific data types.
      def type_to_sql(type, limit = nil, precision = nil, scale = nil)
638
        case type.to_s
639 640 641 642 643 644 645
        when 'binary'
          case limit
          when 0..0xfff;           "varbinary(#{limit})"
          when nil;                "blob"
          when 0x1000..0xffffffff; "blob(#{limit})"
          else raise(ActiveRecordError, "No binary type has character length #{limit}")
          end
646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664
        when 'integer'
          case limit
          when 1; 'tinyint'
          when 2; 'smallint'
          when 3; 'mediumint'
          when nil, 4, 11; 'int(11)'  # compatibility with MySQL default
          when 5..8; 'bigint'
          else raise(ActiveRecordError, "No integer type has byte size #{limit}")
          end
        when 'text'
          case limit
          when 0..0xff;               'tinytext'
          when nil, 0x100..0xffff;    'text'
          when 0x10000..0xffffff;     'mediumtext'
          when 0x1000000..0xffffffff; 'longtext'
          else raise(ActiveRecordError, "No text type has character length #{limit}")
          end
        else
          super
665 666 667 668 669
        end
      end

      # SHOW VARIABLES LIKE 'name'
      def show_variable(name)
K
kennyj 已提交
670
        variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA')
671 672 673 674 675
        variables.first['Value'] unless variables.empty?
      end

      # Returns a table's primary key and belonging sequence.
      def pk_and_sequence_for(table)
K
kennyj 已提交
676 677
        execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
          create_table = each_hash(result).first[:"Create Table"]
678
          if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
S
Seamus Abshere 已提交
679
            keys = $1.split(",").map { |key| key.delete('`"') }
K
kennyj 已提交
680 681 682 683
            keys.length == 1 ? [keys.first, nil] : nil
          else
            nil
          end
684 685 686 687 688 689 690 691 692
        end
      end

      # Returns just a table's primary key
      def primary_key(table)
        pk_and_sequence = pk_and_sequence_for(table)
        pk_and_sequence && pk_and_sequence.first
      end

693 694
      def case_sensitive_modifier(node, table_attribute)
        node = Arel::Nodes.build_quoted node, table_attribute
695 696 697
        Arel::Nodes::Bin.new(node)
      end

698 699 700 701 702 703 704 705
      def case_sensitive_comparison(table, attribute, column, value)
        if column.case_sensitive?
          table[attribute].eq(value)
        else
          super
        end
      end

706 707 708 709 710 711 712 713
      def case_insensitive_comparison(table, attribute, column, value)
        if column.case_sensitive?
          super
        else
          table[attribute].eq(value)
        end
      end

714
      def strict_mode?
715
        self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
716 717
      end

718 719 720 721
      def valid_type?(type)
        !native_database_types[type].nil?
      end

722 723
      protected

S
Sean Griffin 已提交
724
      def initialize_type_map(m) # :nodoc:
725
        super
726

727 728
        register_class_with_limit m, %r(char)i, MysqlString

729 730 731 732 733 734
        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)
735 736
        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 已提交
737 738 739
        m.register_type %r(^float)i,     Type::Float.new(limit: 24)
        m.register_type %r(^double)i,    Type::Float.new(limit: 53)

740 741 742 743 744 745
        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

746 747 748 749
        m.alias_type %r(tinyint\(1\))i,  'boolean' if emulate_booleans
        m.alias_type %r(set)i,           'varchar'
        m.alias_type %r(year)i,          'integer'
        m.alias_type %r(bit)i,           'binary'
750 751 752 753

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

758 759 760 761 762 763 764 765 766 767
      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

768
      def extract_precision(sql_type)
769
        if /time/ === sql_type
770 771 772 773 774 775
          super || 0
        else
          super
        end
      end

S
Sean Griffin 已提交
776 777 778 779
      def fetch_type_metadata(sql_type, collation = "", extra = "")
        MysqlTypeMetadata.new(super(sql_type), collation: collation, extra: extra, strict: strict_mode?)
      end

780 781 782 783 784 785 786 787 788 789 790
      # 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)
        subselect.from subsubselect.as('__active_record_temp')
      end

791 792 793 794
      def add_index_length(option_strings, column_names, options = {})
        if options.is_a?(Hash) && length = options[:length]
          case length
          when Hash
795
            column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
796 797 798 799 800 801 802 803
          when Fixnum
            column_names.each {|name| option_strings[name] += "(#{length})"}
          end
        end

        return option_strings
      end

804
      def quoted_columns_for_index(column_names, options = {})
805
        option_strings = Hash[column_names.map {|name| [name, '']}]
806

807 808 809 810 811 812 813
        # 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]}
814 815 816 817 818 819 820 821 822 823 824 825 826 827
      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 = {})
828
        td = create_table_definition(table_name)
829 830
        cd = td.new_column_definition(column_name, type, options)
        schema_creation.visit_AddColumn cd
831 832 833 834 835 836 837 838 839 840 841 842 843
      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

844 845 846
        td = create_table_definition(table_name)
        cd = td.new_column_definition(column.name, type, options)
        schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
847 848 849
      end

      def rename_column_sql(table_name, column_name, new_column_name)
850
        column  = column_for(table_name, column_name)
851 852 853
        options = {
          default: column.default,
          null: column.null,
854
          auto_increment: column.auto_increment?
855
        }
856

K
kennyj 已提交
857
        current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
858 859 860
        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))
861 862
      end

M
Marc-Andre Lafortune 已提交
863 864 865 866 867 868
      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) }
869 870 871 872 873 874 875 876 877 878 879 880
      end

      def add_index_sql(table_name, column_name, options = {})
        index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
        "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
      end

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

881 882
      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)]
883 884
      end

885
      def remove_timestamps_sql(table_name, options = {})
886 887 888
        [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
      end

889 890
      private

891
      def version
892
        @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map(&:to_i)
893 894 895
      end

      def mariadb?
896
        full_version =~ /mariadb/i
897 898 899 900 901 902
      end

      def supports_rename_index?
        mariadb? ? false : (version[0] == 5 && version[1] >= 7) || version[0] >= 6
      end

903
      def configure_connection
904
        variables = @config.fetch(:variables, {}).stringify_keys
905 906 907

        # By default, MySQL 'where id is null' selects the last inserted id.
        # Turn this off. http://dev.rubyonrails.org/ticket/6778
908
        variables['sql_auto_is_null'] = 0
909 910 911 912

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

        # Make MySQL reject illegal values rather than truncating or blanking them, see
        # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
        # If the user has provided another value for sql_mode, don't replace it.
918 919
        unless variables.has_key?('sql_mode')
          variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
920 921 922 923 924
        end

        # NAMES does not have an equals sign, see
        # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
        # (trailing comma because variable_assignments will always have content)
925 926 927 928 929
        if @config[:encoding]
          encoding = "NAMES #{@config[:encoding]}"
          encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
          encoding << ", "
        end
930 931 932 933

        # Gather up all of the SET variables...
        variable_assignments = variables.map do |k, v|
          if v == ':default' || v == :default
934
            "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
935
          elsif !v.nil?
936
            "@@SESSION.#{k} = #{quote(v)}"
937 938 939 940 941
          end
          # or else nil; compact to clear nils out
        end.compact.join(', ')

        # ...and send them all in one query
942
        @connection.query  "SET #{encoding} #{variable_assignments}"
943
      end
944 945 946 947 948 949 950 951 952

      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
953

954
      def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
955 956 957
        TableDefinition.new(native_database_types, name, temporary, options, as)
      end

958
      class MysqlString < Type::String # :nodoc:
959
        def serialize(value)
960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976
          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
977

978 979
      ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql)
      ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
980 981 982
    end
  end
end