method
default_uniqueness_comparison

v6.0.0 -
Show latest stable
-
0 notes -
Class: AbstractMysqlAdapter
- 1.0.0
- 1.1.6
- 1.2.6
- 2.0.3
- 2.1.0
- 2.2.1
- 2.3.8
- 3.0.0
- 3.0.9
- 3.1.0
- 3.2.1
- 3.2.8
- 3.2.13
- 4.0.2
- 4.1.8
- 4.2.1
- 4.2.7
- 4.2.9
- 5.0.0.1
- 5.1.7
- 5.2.3
- 6.0.0 (0)
- 6.1.3.1
- 6.1.7.7
- 7.0.0
- 7.1.3.2
- 7.1.3.4
- What's this?
default_uniqueness_comparison(attribute, value, klass)
public
Hide source
# File activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 451 def default_uniqueness_comparison(attribute, value, klass) # :nodoc: column = column_for_attribute(attribute) if column.collation && !column.case_sensitive? && !value.nil? ActiveSupport::Deprecation.warn(<<~MSG.squish) Uniqueness validator will no longer enforce case sensitive comparison in Rails 6.1. To continue case sensitive comparison on the :#{attribute.name} attribute in #{klass} model, pass `case_sensitive: true` option explicitly to the uniqueness validator. MSG attribute.eq(Arel::Nodes::Bin.new(value)) else super end end def case_sensitive_comparison(attribute, value) # :nodoc: column = column_for_attribute(attribute) if column.collation && !column.case_sensitive? attribute.eq(Arel::Nodes::Bin.new(value)) else super end end def can_perform_case_insensitive_comparison_for?(column) column.case_sensitive? end private :can_perform_case_insensitive_comparison_for? # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for # distinct queries, and requires that the ORDER BY include the distinct column. # See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html def columns_for_distinct(columns, orders) # :nodoc: order_columns = orders.reject(&:blank?).map { |s| # Convert Arel node to string s = s.to_sql unless s.is_a?(String) # Remove any ASC/DESC modifiers s.gsub(/\s+(?:ASC|DESC)\b/, "") }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" } (order_columns << super).join(", ") end def strict_mode? self.class.type_cast_config_to_boolean(@config.fetch(:strict, true)) end def default_index_type?(index) # :nodoc: index.using == :btree || super end def build_insert_sql(insert) # :nodoc: sql = +"INSERT #{insert.into} #{insert.values_list}" if insert.skip_duplicates? no_op_column = quote_column_name(insert.keys.first) sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}" elsif insert.update_duplicates? sql << " ON DUPLICATE KEY UPDATE " sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",") end sql end def check_version # :nodoc: if database_version < "5.5.8" raise "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8." end end private def initialize_type_map(m = type_map) super register_class_with_limit m, %(char), MysqlString m.register_type %(tinytext), Type::Text.new(limit: 2**8 - 1) m.register_type %(tinyblob), Type::Binary.new(limit: 2**8 - 1) m.register_type %(text), Type::Text.new(limit: 2**16 - 1) m.register_type %(blob), Type::Binary.new(limit: 2**16 - 1) m.register_type %(mediumtext), Type::Text.new(limit: 2**24 - 1) m.register_type %(mediumblob), Type::Binary.new(limit: 2**24 - 1) m.register_type %(longtext), Type::Text.new(limit: 2**32 - 1) m.register_type %(longblob), Type::Binary.new(limit: 2**32 - 1) m.register_type %(^float), Type::Float.new(limit: 24) m.register_type %(^double), Type::Float.new(limit: 53) register_integer_type m, %(^bigint), limit: 8 register_integer_type m, %(^int), limit: 4 register_integer_type m, %(^mediumint), limit: 3 register_integer_type m, %(^smallint), limit: 2 register_integer_type m, %(^tinyint), limit: 1 m.register_type %(^tinyint\(1\)), Type::Boolean.new if emulate_booleans m.alias_type %(year), "integer" m.alias_type %(bit), "binary" m.register_type(%(enum)) do |sql_type| limit = sql_type[/^enum\s*\((.+)\)/, 1] .split(",").map { |enum| enum.strip.length - 2 }.max MysqlString.new(limit: limit) end m.register_type(%(^set)) do |sql_type| limit = sql_type[/^set\s*\((.+)\)/, 1] .split(",").map { |set| set.strip.length - 1 }.sum - 1 MysqlString.new(limit: limit) end end def register_integer_type(mapping, key, options) mapping.register_type(key) do |sql_type| if /\bunsigned\b/.match?(sql_type) Type::UnsignedInteger.new(options) else Type::Integer.new(options) end end end def extract_precision(sql_type) if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type) super || 0 else super end end # See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html ER_DUP_ENTRY = 1062 ER_NOT_NULL_VIOLATION = 1048 ER_NO_REFERENCED_ROW = 1216 ER_ROW_IS_REFERENCED = 1217 ER_DO_NOT_HAVE_DEFAULT = 1364 ER_ROW_IS_REFERENCED_2 = 1451 ER_NO_REFERENCED_ROW_2 = 1452 ER_DATA_TOO_LONG = 1406 ER_OUT_OF_RANGE = 1264 ER_LOCK_DEADLOCK = 1213 ER_CANNOT_ADD_FOREIGN = 1215 ER_CANNOT_CREATE_TABLE = 1005 ER_LOCK_WAIT_TIMEOUT = 1205 ER_QUERY_INTERRUPTED = 1317 ER_QUERY_TIMEOUT = 3024 ER_FK_INCOMPATIBLE_COLUMNS = 3780 def translate_exception(exception, message,, sql,, binds)) case error_number(exception) when ER_DUP_ENTRY RecordNotUnique.new(message, sql: sql, binds: binds) when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2 InvalidForeignKey.new(message, sql: sql, binds: binds) when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS mismatched_foreign_key(message, sql: sql, binds: binds) when ER_CANNOT_CREATE_TABLE if message.include?("errno: 150") mismatched_foreign_key(message, sql: sql, binds: binds) else super end when ER_DATA_TOO_LONG ValueTooLong.new(message, sql: sql, binds: binds) when ER_OUT_OF_RANGE RangeError.new(message, sql: sql, binds: binds) when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT NotNullViolation.new(message, sql: sql, binds: binds) when ER_LOCK_DEADLOCK Deadlocked.new(message, sql: sql, binds: binds) when ER_LOCK_WAIT_TIMEOUT LockWaitTimeout.new(message, sql: sql, binds: binds) when ER_QUERY_TIMEOUT StatementTimeout.new(message, sql: sql, binds: binds) when ER_QUERY_INTERRUPTED QueryCanceled.new(message, sql: sql, binds: binds) else super end end def change_column_for_alter(table_name, column_name, type, options = {}) column = column_for(table_name, column_name) type ||= column.sql_type unless options.key?(:default) options[:default] = column.default end unless options.key?(:null) options[:null] = column.null end unless options.key?(:comment) options[:comment] = column.comment end td = create_table_definition(table_name) cd = td.new_column_definition(column.name, type, options) schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) end def rename_column_for_alter(table_name, column_name, new_column_name) column = column_for(table_name, column_name) options = { default: column.default, null: column.null, auto_increment: column.auto_increment? } current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"] 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)) end def add_index_for_alter(table_name, column_name, options = {}) 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}" end def remove_index_for_alter(table_name, options = {}) index_name = index_name_for_remove(table_name, options) "DROP INDEX #{quote_column_name(index_name)}" end def add_timestamps_for_alter(table_name, options = {}) options[:null] = false if options[:null].nil? if !options.key?(:precision) && supports_datetime_with_precision? options[:precision] = 6 end [add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)] end def remove_timestamps_for_alter(table_name, options = {}) [remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)] end def supports_rename_index? mariadb? ? false : database_version >= "5.7.6" end def configure_connection variables = @config.fetch(:variables, {}).stringify_keys # By default, MySQL 'where id is null' selects the last inserted id; Turn this off. variables["sql_auto_is_null"] = 0 # Increase timeout so the server doesn't disconnect us. wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout]) wait_timeout = 2147483 unless wait_timeout.is_a?(Integer) variables["wait_timeout"] = wait_timeout defaults = [":default", :default].to_set # Make MySQL reject illegal values rather than truncating or blanking them, see # https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables # If the user has provided another value for sql_mode, don't replace it. if sql_mode = variables.delete("sql_mode") sql_mode = quote(sql_mode) elsif !defaults.include?(strict_mode?) if strict_mode? sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')" else sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')" sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')" sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')" end sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')" end sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode # NAMES does not have an equals sign, see # https://dev.mysql.com/doc/refman/5.7/en/set-names.html # (trailing comma because variable_assignments will always have content) if @config[:encoding] encoding = +"NAMES #{@config[:encoding]}" encoding << " COLLATE #{@config[:collation]}" if @config[:collation] encoding << ", " end # Gather up all of the SET variables... variable_assignments = variables.map do |k, v| if defaults.include?(v) "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default elsif !v.nil? "@@SESSION.#{k} = #{quote(v)}" end # or else nil; compact to clear nils out end.compact.join(", ") # ...and send them all in one query execute "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}" end def column_definitions(table_name) # :nodoc: execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result| each_hash(result) end end def create_table_info(table_name) # :nodoc: exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"] end def arel_visitor Arel::Visitors::MySQL.new(self) end def build_statement_pool StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit])) end def mismatched_foreign_key(message, sql,, binds)) match = / (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+? FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s* REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\) /mi.match(sql) options = { message: message, sql: sql, binds: binds, } if match options[:table] = match[:table] options[:foreign_key] = match[:foreign_key] options[:target_table] = match[:target_table] options[:primary_key] = match[:primary_key] options[:primary_key_column] = column_for(match[:target_table], match[:primary_key]) end MismatchedForeignKey.new(options) end def version_string(full_version_string) full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1] end class MysqlString < Type::String # :nodoc: def serialize(value) 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 ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2) ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2) end end