diff options
Diffstat (limited to 'activerecord')
50 files changed, 612 insertions, 268 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index b6a943eca7..3663a6faa4 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,36 @@ +* Allow single table inheritance instantiation to work when storing + demodulized class names. + + *Alex Robbin* + +* Correctly pass MySQL options when using structure_dump or structure_load + + Specifically, it fixes an issue when using SSL authentication. + + *Alex Coomans* + +* Dump indexes in `create_table` instead of `add_index`. + + If the adapter supports indexes in create table, generated SQL is + slightly more efficient. + + *Ryuta Kamizono* + +* Correctly dump `:options` on `create_table` for MySQL. + + *Ryuta Kamizono* + +* PostgreSQL: `:collation` support for string and text columns. + + Example: + + create_table :foos do |t| + t.string :string_en, collation: 'en_US.UTF-8' + t.text :text_ja, collation: 'ja_JP.UTF-8' + end + + *Ryuta Kamizono* + * Make `unscope` aware of "less than" and "greater than" conditions. *TAKAHASHI Kazuaki* @@ -745,7 +778,7 @@ *Yves Senn* -* Fix bug with 'ActiveRecord::Type::Numeric' that caused negative values to +* Fix bug with `ActiveRecord::Type::Numeric` that caused negative values to be marked as having changed when set to the same negative value. Closes #18161. diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc index f4777919d3..85d807933a 100644 --- a/activerecord/README.rdoc +++ b/activerecord/README.rdoc @@ -188,7 +188,7 @@ Admit the Database: The latest version of Active Record can be installed with RubyGems: - % [sudo] gem install activerecord + % gem install activerecord Source code can be downloaded as part of the Rails project on GitHub: diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index c5c2178ee2..94f59f9e95 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -185,7 +185,7 @@ module ActiveRecord super end - # Returns the specified association instance if it responds to :loaded?, nil otherwise. + # Returns the specified association instance if it exists, nil otherwise. def association_instance_get(name) @association_cache[name] end @@ -285,7 +285,7 @@ module ActiveRecord # end # # If your model class is <tt>Project</tt>, the module is - # named <tt>Project::GeneratedFeatureMethods</tt>. The GeneratedFeatureMethods module is + # named <tt>Project::GeneratedAssociationMethods</tt>. The GeneratedAssociationMethods module is # included in the model class immediately after the (anonymous) generated attributes methods # module, meaning an association will override the methods for an attribute with the same name. # diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index cc265e2af6..45fdcaa1cd 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -29,7 +29,8 @@ module ActiveRecord assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty? end - # Re-raise with the ActiveRecord constant in case of an error + # Tries to assign given value to given attribute. + # In case of an error, re-raises with the ActiveRecord constant. def _assign_attribute(k, v) # :nodoc: super rescue ActiveModel::UnknownAttributeError diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 8c50f3d1a3..77e64a22be 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -220,7 +220,7 @@ module ActiveRecord include MonitorMixin - attr_accessor :automatic_reconnect, :checkout_timeout + attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache attr_reader :spec, :connections, :size, :reaper # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification @@ -432,7 +432,9 @@ module ActiveRecord end def new_connection - Base.send(spec.adapter_method, spec.config) + Base.send(spec.adapter_method, spec.config).tap do |conn| + conn.schema_cache = schema_cache.dup if schema_cache + end end def current_connection_id #:nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb index c0a2111571..30b2fca2ca 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb @@ -18,9 +18,9 @@ module ActiveRecord end # Returns the maximum allowed length for an index name. This - # limit is enforced by rails and Is less than or equal to + # limit is enforced by \Rails and is less than or equal to # <tt>index_name_length</tt>. The gap between - # <tt>index_name_length</tt> is to allow internal rails + # <tt>index_name_length</tt> is to allow internal \Rails # operations to use prefixes in temporary operations. def allowed_index_name_length index_name_length diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 42c794c828..431fe25501 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -40,8 +40,9 @@ module ActiveRecord # Returns a single value from a record def select_value(arel, name = nil, binds = []) - if result = select_one(arel, name, binds) - result.values.first + arel, binds = binds_from_relation arel, binds + if result = select_rows(to_sql(arel, binds), name, binds).first + result.first end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 91c7298983..2c7409b2dc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -123,6 +123,8 @@ module ActiveRecord 'f' end + # Quote date/time values for use in SQL input. Includes microseconds + # if the value is a Time responding to usec. def quoted_date(value) if value.acts_like?(:time) zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb index f754df93b6..18d943f452 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -14,10 +14,6 @@ module ActiveRecord send m, o end - def visit_AddColumn(o) - "ADD #{accept(o)}" - end - delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql, to: :@conn private :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql @@ -25,7 +21,7 @@ module ActiveRecord def visit_AlterTable(o) sql = "ALTER TABLE #{quote_table_name(o.name)} " - sql << o.adds.map { |col| visit_AddColumn col }.join(' ') + sql << o.adds.map { |col| accept col }.join(' ') sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(' ') sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(' ') end @@ -37,6 +33,10 @@ module ActiveRecord column_sql end + def visit_AddColumnDefinition(o) + "ADD #{accept(o.column)}" + end + def visit_TableDefinition(o) create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE " create_sql << "#{quote_table_name(o.name)} " @@ -70,6 +70,7 @@ module ActiveRecord column_options[:after] = o.after column_options[:auto_increment] = o.auto_increment column_options[:primary_key] = o.primary_key + column_options[:collation] = o.collation column_options end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 4761024ad0..0ccf0c498b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -15,13 +15,16 @@ module ActiveRecord # are typically created by methods in TableDefinition, and added to the # +columns+ attribute of said TableDefinition object, in order to be used # for generating a number of table creation or table changing SQL statements. - class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :sql_type) #:nodoc: + class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type) #:nodoc: def primary_key? primary_key || type.to_sym == :primary_key end end + class AddColumnDefinition < Struct.new(:column) # :nodoc: + end + class ChangeColumnDefinition < Struct.new(:column, :name) #:nodoc: end @@ -227,7 +230,7 @@ module ActiveRecord # The +type+ parameter is normally one of the migrations native types, # which is one of the following: # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>, - # <tt>:integer</tt>, <tt>:float</tt>, <tt>:decimal</tt>, + # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, # <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>, # <tt>:binary</tt>, <tt>:boolean</tt>. # @@ -434,6 +437,7 @@ module ActiveRecord column.after = options[:after] column.auto_increment = options[:auto_increment] column.primary_key = type == :primary_key || options[:primary_key] + column.collation = options[:collation] column end @@ -476,7 +480,7 @@ module ActiveRecord def add_column(name, type, options) name = name.to_s type = type.to_sym - @adds << @td.new_column_definition(name, type, options) + @adds << AddColumnDefinition.new(@td.new_column_definition(name, type, options)) end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb index 999cb0ec5a..deb014ad46 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -35,12 +35,16 @@ module ActiveRecord default = schema_default(column) if column.has_default? spec[:default] = default unless default.nil? + if collation = schema_collation(column) + spec[:collation] = collation + end + spec end # Lists the valid migration options def migration_keys - [:name, :limit, :precision, :scale, :default, :null] + [:name, :limit, :precision, :scale, :default, :null, :collation] end private @@ -56,6 +60,10 @@ module ActiveRecord type.type_cast_for_schema(default) end end + + def schema_collation(column) + column.collation.inspect if column.collation + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 879a47f021..9004d86b04 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -14,6 +14,10 @@ module ActiveRecord {} end + def table_options(table_name) + nil + end + # Truncates a table alias according to the limits of the current adapter. def table_alias_for(table_name) table_name[0...table_alias_length].tr('.', '_') @@ -930,13 +934,13 @@ module ActiveRecord def add_index_options(table_name, column_name, options = {}) #:nodoc: column_names = Array(column_name) - index_name = index_name(table_name, column: column_names) options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type) - index_type = options[:unique] ? "UNIQUE" : "" index_type = options[:type].to_s if options.key?(:type) + index_type ||= options[:unique] ? "UNIQUE" : "" index_name = options[:name].to_s if options.key?(:name) + index_name ||= index_name(table_name, column: column_names) max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length if options.key?(:algorithm) @@ -1049,7 +1053,7 @@ module ActiveRecord end end - def validate_index_length!(table_name, new_name) + def validate_index_length!(table_name, new_name) # :nodoc: if new_name.length > allowed_index_name_length raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index ae42e8ef8d..0705c22a8c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -387,8 +387,8 @@ module ActiveRecord end end - def new_column(name, default, sql_type_metadata = nil, null = true) - Column.new(name, default, sql_type_metadata, null) + def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) + Column.new(name, default, sql_type_metadata, null, default_function, collation) end def lookup_cast_type(sql_type) # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 76aee452ca..0862c7678e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -14,7 +14,7 @@ module ActiveRecord end class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition - attr_accessor :charset, :collation + attr_accessor :charset end class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition @@ -28,7 +28,6 @@ module ActiveRecord column.auto_increment = true end column.charset = options[:charset] - column.collation = options[:collation] column end @@ -44,10 +43,6 @@ module ActiveRecord end class SchemaCreation < AbstractAdapter::SchemaCreation - def visit_AddColumn(o) - add_column_position!(super, column_options(o)) - end - private def visit_DropForeignKey(name) @@ -67,6 +62,10 @@ module ActiveRecord create_sql end + def visit_AddColumnDefinition(o) + add_column_position!(super, column_options(o.column)) + end + def visit_ChangeColumnDefinition(o) change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}" add_column_position!(change_column_sql, column_options(o.column)) @@ -75,7 +74,6 @@ module ActiveRecord def column_options(o) column_options = super column_options[:charset] = o.charset - column_options[:collation] = o.collation column_options end @@ -99,8 +97,8 @@ module ActiveRecord end def index_in_create(table_name, column_name, options) - index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options) - "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}" + 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}) " end end @@ -128,20 +126,20 @@ module ActiveRecord spec = super spec.delete(:precision) if /time/ === column.sql_type && column.precision == 0 spec.delete(:limit) if :boolean === column.type + spec + end + + def schema_collation(column) 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"] - spec[:collation] = column.collation.inspect if column.collation != @collation_cache[table_name] + column.collation.inspect if column.collation != @collation_cache[table_name] end - spec - end - - def migration_keys - super + [:collation] end + private :schema_collation class Column < ConnectionAdapters::Column # :nodoc: - delegate :strict, :collation, :extra, to: :sql_type_metadata, allow_nil: true + delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true def initialize(*) super @@ -195,12 +193,11 @@ module ActiveRecord end class MysqlTypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc: - attr_reader :collation, :extra, :strict + attr_reader :extra, :strict - def initialize(type_metadata, collation: "", extra: "", strict: false) + def initialize(type_metadata, extra: "", strict: false) super(type_metadata) @type_metadata = type_metadata - @collation = collation @extra = extra @strict = strict end @@ -218,7 +215,7 @@ module ActiveRecord protected def attributes_for_hash - [self.class, @type_metadata, collation, extra, strict] + [self.class, @type_metadata, extra, strict] end end @@ -342,8 +339,8 @@ module ActiveRecord raise NotImplementedError end - def new_column(field, default, sql_type_metadata = nil, null = true) # :nodoc: - Column.new(field, default, sql_type_metadata, null) + 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) end # Must return the MySQL error number from the exception, if the exception has an @@ -566,8 +563,8 @@ module ActiveRecord each_hash(result).map do |field| field_name = set_field_encoding(field[:Field]) sql_type = field[:Type] - type_metadata = fetch_type_metadata(sql_type, field[:Collation], field[:Extra]) - new_column(field_name, field[:Default], type_metadata, field[:Null] == "YES") + type_metadata = fetch_type_metadata(sql_type, field[:Extra]) + new_column(field_name, field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation]) end end end @@ -634,7 +631,7 @@ module ActiveRecord change_column table_name, column_name, column.sql_type, :default => default end - def change_column_null(table_name, column_name, null, default = nil) + def change_column_null(table_name, column_name, null, default = nil) #:nodoc: column = column_for(table_name, column_name) unless null || default.nil? @@ -654,8 +651,8 @@ module ActiveRecord end def add_index(table_name, column_name, options = {}) #:nodoc: - 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}" + 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}" end def foreign_keys(table_name) @@ -686,33 +683,25 @@ module ActiveRecord end end + def table_options(table_name) + create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] + + # 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 + # Maps logical Rails types to MySQL-specific data types. def type_to_sql(type, limit = nil, precision = nil, scale = nil) case type.to_s 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 + binary_to_sql(limit) 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 + integer_to_sql(limit) 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 + text_to_sql(limit) else super end @@ -826,21 +815,8 @@ module ActiveRecord end end - def fetch_type_metadata(sql_type, collation = "", extra = "") - MysqlTypeMetadata.new(super(sql_type), collation: collation, extra: extra, strict: strict_mode?) - end - - # 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') + def fetch_type_metadata(sql_type, extra = "") + MysqlTypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?) end def add_index_length(option_strings, column_names, options = {}) @@ -882,7 +858,7 @@ module ActiveRecord def add_column_sql(table_name, column_name, type, options = {}) td = create_table_definition(table_name) cd = td.new_column_definition(column_name, type, options) - schema_creation.visit_AddColumn cd + schema_creation.accept(AddColumnDefinition.new(cd)) end def change_column_sql(table_name, column_name, type, options = {}) @@ -924,8 +900,9 @@ module ActiveRecord 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})" + 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_sql(table_name, options = {}) @@ -943,6 +920,19 @@ module ActiveRecord private + # 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 + def version @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map(&:to_i) end @@ -1009,6 +999,36 @@ module ActiveRecord TableDefinition.new(native_database_types, name, temporary, options, as) end + def binary_to_sql(limit) # :nodoc: + 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 + end + + def integer_to_sql(limit) # :nodoc: + 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 + 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' + else raise(ActiveRecordError, "No text type has character length #{limit}") + end + end + class MysqlString < Type::String # :nodoc: def serialize(value) case value diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index f4dda5154e..4b95b0681d 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -12,7 +12,7 @@ module ActiveRecord ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/ end - attr_reader :name, :null, :sql_type_metadata, :default, :default_function + attr_reader :name, :null, :sql_type_metadata, :default, :default_function, :collation delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true @@ -22,12 +22,13 @@ module ActiveRecord # +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>. # +sql_type_metadata+ is various information about the type of the column # +null+ determines if this column allows +NULL+ values. - def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil) + def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) @name = name @sql_type_metadata = sql_type_metadata @null = null @default = default @default_function = default_function + @collation = collation @table_name = nil end @@ -60,7 +61,7 @@ module ActiveRecord protected def attributes_for_hash - [self.class, name, default, sql_type_metadata, null, default_function] + [self.class, name, default, sql_type_metadata, null, default_function, collation] end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 21631be25c..e97e82f056 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -1,6 +1,6 @@ require 'active_record/connection_adapters/abstract_mysql_adapter' -gem 'mysql2', '~> 0.3.13' +gem 'mysql2', '~> 0.3.18' require 'mysql2' module ActiveRecord diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index b7755c4593..f175730551 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -40,8 +40,7 @@ module ActiveRecord PGconn.quote_ident(name.to_s) end - # Quote date/time values for use in SQL input. Includes microseconds - # if the value is a Time responding to usec. + # Quote date/time values for use in SQL input. def quoted_date(value) #:nodoc: if value.year <= 0 bce_year = format("%04d", -value.year + 1) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index 168180cfd3..412999f0fe 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -8,6 +8,13 @@ module ActiveRecord o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.array) super end + + def add_column_options!(sql, options) + if options[:collation] + sql << " COLLATE \"#{options[:collation]}\"" + end + super + end end module SchemaStatements @@ -63,7 +70,7 @@ module ActiveRecord # Returns the list of all tables in the schema search path or a specified schema. def tables(name = nil) - query(<<-SQL, 'SCHEMA').map { |row| row[0] } + select_values(<<-SQL, 'SCHEMA') SELECT tablename FROM pg_tables WHERE schemaname = ANY (current_schemas(false)) @@ -77,7 +84,7 @@ module ActiveRecord name = Utils.extract_schema_qualified_name(name.to_s) return false unless name.identifier - exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0 + select_value(<<-SQL, 'SCHEMA').to_i > 0 SELECT COUNT(*) FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace @@ -93,15 +100,16 @@ module ActiveRecord # Returns true if schema exists. def schema_exists?(name) - exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0 + select_value(<<-SQL, 'SCHEMA').to_i > 0 SELECT COUNT(*) FROM pg_namespace WHERE nspname = '#{name}' SQL end + # Verifies existence of an index with a given name. def index_name_exists?(table_name, index_name, default) - exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0 + select_value(<<-SQL, 'SCHEMA').to_i > 0 SELECT COUNT(*) FROM pg_class t INNER JOIN pg_index d ON t.oid = d.indrelid @@ -158,33 +166,33 @@ module ActiveRecord # Returns the list of all column definitions for a table. def columns(table_name) # Limit, precision, and scale are all handled by the superclass. - column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod| + column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation| oid = oid.to_i fmod = fmod.to_i type_metadata = fetch_type_metadata(column_name, type, oid, fmod) default_value = extract_value_from_default(default) default_function = extract_default_function(default_value, default) - new_column(column_name, default_value, type_metadata, !notnull, default_function) + new_column(column_name, default_value, type_metadata, !notnull, default_function, collation) end end - def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil) # :nodoc: - PostgreSQLColumn.new(name, default, sql_type_metadata, null, default_function) + def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc: + PostgreSQLColumn.new(name, default, sql_type_metadata, null, default_function, collation) end # Returns the current database name. def current_database - query('select current_database()', 'SCHEMA')[0][0] + select_value('select current_database()', 'SCHEMA') end # Returns the current schema name. def current_schema - query('SELECT current_schema', 'SCHEMA')[0][0] + select_value('SELECT current_schema', 'SCHEMA') end # Returns the current database encoding format. def encoding - query(<<-end_sql, 'SCHEMA')[0][0] + select_value(<<-end_sql, 'SCHEMA') SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database WHERE pg_database.datname LIKE '#{current_database}' end_sql @@ -192,21 +200,21 @@ module ActiveRecord # Returns the current database collation. def collation - query(<<-end_sql, 'SCHEMA')[0][0] + select_value(<<-end_sql, 'SCHEMA') SELECT pg_database.datcollate FROM pg_database WHERE pg_database.datname LIKE '#{current_database}' end_sql end # Returns the current database ctype. def ctype - query(<<-end_sql, 'SCHEMA')[0][0] + select_value(<<-end_sql, 'SCHEMA') SELECT pg_database.datctype FROM pg_database WHERE pg_database.datname LIKE '#{current_database}' end_sql end # Returns an array of schema names. def schema_names - query(<<-SQL, 'SCHEMA').flatten + select_values(<<-SQL, 'SCHEMA') SELECT nspname FROM pg_namespace WHERE nspname !~ '^pg_.*' @@ -239,12 +247,12 @@ module ActiveRecord # Returns the active schema search path. def schema_search_path - @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0] + @schema_search_path ||= select_value('SHOW search_path', 'SCHEMA') end # Returns the current client message level. def client_min_messages - query('SHOW client_min_messages', 'SCHEMA')[0][0] + select_value('SHOW client_min_messages', 'SCHEMA') end # Set the client message level. @@ -262,10 +270,7 @@ module ActiveRecord end def serial_sequence(table, column) - result = exec_query(<<-eosql, 'SCHEMA') - SELECT pg_get_serial_sequence('#{table}', '#{column}') - eosql - result.rows.first.first + select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", 'SCHEMA') end # Sets the sequence of a table's primary key to the specified value. @@ -276,9 +281,7 @@ module ActiveRecord if sequence quoted_sequence = quote_table_name(sequence) - select_value <<-end_sql, 'SCHEMA' - SELECT setval('#{quoted_sequence}', #{value}) - end_sql + select_value("SELECT setval('#{quoted_sequence}', #{value})", 'SCHEMA') else @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger end @@ -301,7 +304,7 @@ module ActiveRecord if pk && sequence quoted_sequence = quote_table_name(sequence) - select_value <<-end_sql, 'SCHEMA' + select_value(<<-end_sql, 'SCHEMA') SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false) end_sql end @@ -363,7 +366,7 @@ module ActiveRecord # Returns just a table's primary key def primary_key(table) - pks = exec_query(<<-end_sql, 'SCHEMA').rows + pks = query(<<-end_sql, 'SCHEMA') SELECT attr.attname FROM pg_attribute attr INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey) @@ -395,20 +398,20 @@ module ActiveRecord rename_table_indexes(table_name, new_name) end - # Adds a new column to the named table. - # See TableDefinition#column for details of the options you can use. - def add_column(table_name, column_name, type, options = {}) + def add_column(table_name, column_name, type, options = {}) #:nodoc: clear_cache! super end - # Changes the column of a table. - def change_column(table_name, column_name, type, options = {}) + def change_column(table_name, column_name, type, options = {}) #:nodoc: clear_cache! quoted_table_name = quote_table_name(table_name) quoted_column_name = quote_column_name(column_name) sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale], options[:array]) sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}" + if options[:collation] + sql << " COLLATE \"#{options[:collation]}\"" + end if options[:using] sql << " USING #{options[:using]}" elsif options[:cast_as] @@ -437,7 +440,7 @@ module ActiveRecord end end - def change_column_null(table_name, column_name, null, default = nil) + def change_column_null(table_name, column_name, null, default = nil) #:nodoc: clear_cache! unless null || default.nil? column = column_for(table_name, column_name) @@ -447,7 +450,7 @@ module ActiveRecord end # Renames a column in a table. - def rename_column(table_name, column_name, new_column_name) + def rename_column(table_name, column_name, new_column_name) #:nodoc: clear_cache! execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}" rename_column_indexes(table_name, column_name, new_column_name) @@ -462,6 +465,8 @@ module ActiveRecord execute "DROP INDEX #{quote_table_name(index_name)}" end + # Renames an index of a table. Raises error if length of new + # index name is greater than allowed limit. def rename_index(table_name, old_name, new_name) validate_index_length!(table_name, new_name) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 332ac9d88c..2b33a5b9cb 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -770,9 +770,11 @@ module ActiveRecord # - format_type includes the column size constraint, e.g. varchar(50) # - ::regclass is a function that gives the id for a table name def column_definitions(table_name) # :nodoc: - exec_query(<<-end_sql, 'SCHEMA').rows + query(<<-end_sql, 'SCHEMA') SELECT a.attname, format_type(a.atttypid, a.atttypmod), - pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod + pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod, + (SELECT c.collname FROM pg_collation c, pg_type t + WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation) FROM pg_attribute a LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb index 37ff4e4613..981d5d7a3c 100644 --- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb @@ -13,6 +13,14 @@ module ActiveRecord @tables = {} end + def initialize_dup(other) + super + @columns = @columns.dup + @columns_hash = @columns_hash.dup + @primary_keys = @primary_keys.dup + @tables = @tables.dup + end + def primary_keys(table_name) @primary_keys[table_name] ||= table_exists?(table_name) ? connection.primary_key(table_name) : nil end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 7e184dd510..3186769510 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -450,7 +450,7 @@ module ActiveRecord end end - def change_column_null(table_name, column_name, null, default = nil) + def change_column_null(table_name, column_name, null, default = nil) #:nodoc: unless null || default.nil? exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 1ad910c4bc..8a014e682e 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -88,9 +88,10 @@ module ActiveRecord ## # :singleton-method: # Specifies which database schemas to dump when calling db:structure:dump. - # If :schema_search_path (the default), it will dumps any schemas listed in schema_search_path. - # Use :all to always dumps all schemas regardless of the schema_search_path. - # A string of comma separated schemas can also be used to pass a custom list of schemas. + # If the value is :schema_search_path (the default), any schemas listed in + # schema_search_path are dumped. Use :all to dump all schemas regardless + # of schema_search_path, or a string of comma separated schemas for a + # custom list. mattr_accessor :dump_schemas, instance_writer: false self.dump_schemas = :schema_search_path diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 98aee77557..0f1759abaa 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -71,9 +71,9 @@ module ActiveRecord class RecordNotDestroyed < ActiveRecordError attr_reader :record - def initialize(record) + def initialize(message, record = nil) @record = record - super() + super(message) end end diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 24098f72dc..e613d157aa 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -198,14 +198,16 @@ module ActiveRecord def subclass_from_attributes(attrs) subclass_name = attrs.with_indifferent_access[inheritance_column] - if subclass_name.present? && subclass_name != self.name - subclass = subclass_name.safe_constantize + if subclass_name.present? + subclass = find_sti_class(subclass_name) - unless descendants.include?(subclass) - raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass_name} is not a subclass of #{name}") - end + if subclass.name != self.name + unless descendants.include?(subclass) + raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}") + end - subclass + subclass + end end end end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index a83b90a95f..ef446ce596 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -307,9 +307,8 @@ module ActiveRecord # # == Reversible Migrations # - # Starting with Rails 3.1, you will be able to define reversible migrations. # Reversible migrations are migrations that know how to go +down+ for you. - # You simply supply the +up+ logic, and the Migration system will figure out + # You simply supply the +up+ logic, and the Migration system figures out # how to execute the down commands for you. # # To define a reversible migration, define the +change+ method in your diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index da8f4d027a..466175690e 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -148,7 +148,7 @@ module ActiveRecord # Attributes marked as readonly are silently ignored if the record is # being updated. def save!(*args) - create_or_update(*args) || raise(RecordNotSaved.new(nil, self)) + create_or_update(*args) || raise(RecordNotSaved.new("Failed to save the record", self)) end # Deletes the record in the database and freezes this instance to @@ -193,7 +193,7 @@ module ActiveRecord # and #destroy! raises ActiveRecord::RecordNotDestroyed. # See ActiveRecord::Callbacks for further details. def destroy! - destroy || raise(ActiveRecord::RecordNotDestroyed, self) + destroy || raise(RecordNotDestroyed.new("Failed to destroy the record", self)) end # Returns an instance of the specified +klass+ with the attributes of the diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 7e907beec0..5af64b717a 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -93,6 +93,7 @@ module ActiveRecord cache = Marshal.load File.binread filename if cache.version == ActiveRecord::Migrator.current_version self.connection.schema_cache = cache + self.connection_pool.schema_cache = cache.dup else warn "Ignoring db/schema_cache.dump because it has expired. The current schema version is #{ActiveRecord::Migrator.current_version}, but the one in the cache is #{cache.version}." end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 576a32bf75..2001a89ffb 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -111,23 +111,11 @@ module ActiveRecord # Find the first record (or first N records if a parameter is supplied). # If no order is defined it will order by primary key. # - # Person.first # returns the first object fetched by SELECT * FROM people + # Person.first # returns the first object fetched by SELECT * FROM people ORDER BY people.id LIMIT 1 # Person.where(["user_name = ?", user_name]).first # Person.where(["user_name = :u", { u: user_name }]).first # Person.order("created_on DESC").offset(5).first - # Person.first(3) # returns the first three objects fetched by SELECT * FROM people LIMIT 3 - # - # ==== Rails 3 - # - # Person.first # SELECT "people".* FROM "people" LIMIT 1 - # - # NOTE: Rails 3 may not order this query by the primary key and the order - # will depend on the database implementation. In order to ensure that behavior, - # use <tt>User.order(:id).first</tt> instead. - # - # ==== Rails 4 - # - # Person.first # SELECT "people".* FROM "people" ORDER BY "people"."id" ASC LIMIT 1 + # Person.first(3) # returns the first three objects fetched by SELECT * FROM people ORDER BY people.id LIMIT 3 # def first(limit = nil) if limit diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index c7f55ebaa1..c2567311bd 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -11,17 +11,15 @@ module ActiveRecord protected - # Accepts an array, hash, or string of SQL conditions and sanitizes + # Accepts an array or string of SQL conditions and sanitizes # them into a valid SQL fragment for a WHERE clause. # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'" - # { name: "foo'bar", group_id: 4 } returns "name='foo''bar' and group_id='4'" # "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'" def sanitize_sql_for_conditions(condition, table_name = self.table_name) return nil if condition.blank? case condition when Array; sanitize_sql_array(condition) - when Hash; sanitize_sql_hash_for_conditions(condition, table_name) else condition end end diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index eaeaf0321b..a4a986e6ed 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -131,6 +131,10 @@ HEADER tbl.print ", id: false" end tbl.print ", force: :cascade" + + table_options = @connection.table_options(table) + tbl.print ", options: #{table_options.inspect}" unless table_options.blank? + tbl.puts " do |t|" # then dump all non-primary key columns @@ -168,11 +172,11 @@ HEADER tbl.puts end + indexes(table, tbl) + tbl.puts " end" tbl.puts - indexes(table, tbl) - tbl.rewind stream.print tbl.read rescue => e @@ -188,8 +192,7 @@ HEADER if (indexes = @connection.indexes(table)).any? add_index_statements = indexes.map do |index| statement_parts = [ - "add_index #{remove_prefix_and_suffix(index.table).inspect}", - index.columns.inspect, + "t.index #{index.columns.inspect}", "name: #{index.name.inspect}", ] statement_parts << 'unique: true' if index.unique @@ -203,11 +206,10 @@ HEADER statement_parts << "using: #{index.using.inspect}" if index.using statement_parts << "type: #{index.type.inspect}" if index.type - " #{statement_parts.join(', ')}" + " #{statement_parts.join(', ')}" end stream.puts add_index_statements.sort.join("\n") - stream.puts end end diff --git a/activerecord/lib/active_record/secure_token.rb b/activerecord/lib/active_record/secure_token.rb index a3023a0cb4..ca11853da7 100644 --- a/activerecord/lib/active_record/secure_token.rb +++ b/activerecord/lib/active_record/secure_token.rb @@ -13,7 +13,7 @@ module ActiveRecord # # user = User.new # user.save - # user.token # => "4kUgL2pdQMSCQtjE" + # user.token # => "pX27zsMN2ViQKta1bGfLmVJE" # user.auth_token # => "77TMHrHJFvFDwodq8w7Ev2m7" # user.regenerate_token # => true # user.regenerate_auth_token # => true diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index eafbb2c249..bc80275a88 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -130,15 +130,21 @@ IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION; end def prepare_command_options(command) - args = [command] - args.concat(['--user', configuration['username']]) if configuration['username'] - args << "--password=#{configuration['password']}" if configuration['password'] - args.concat(['--default-character-set', configuration['encoding']]) if configuration['encoding'] - configuration.slice('host', 'port', 'socket').each do |k, v| - args.concat([ "--#{k}", v.to_s ]) if v - end - - args + args = { + 'host' => '--host', + 'port' => '--port', + 'socket' => '--socket', + 'username' => '--user', + 'password' => '--password', + 'encoding' => '--default-character-set', + 'sslca' => '--ssl-ca', + 'sslcert' => '--ssl-cert', + 'sslcapath' => '--ssl-capath', + 'sslcipher' => '--ssh-cipher', + 'sslkey' => '--ssl-key' + }.map { |opt, arg| "#{arg}=#{configuration[opt]}" if configuration[opt] }.compact + + [command, *args] end end end diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb index 7a3c6f5e95..0d57de4d65 100644 --- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb +++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb @@ -14,10 +14,9 @@ module ActiveRecord protected attr_reader :migration_action, :join_tables - # sets the default migration template that is being used for the generation of the migration - # depending on the arguments which would be sent out in the command line, the migration template - # and the table name instance variables are setup. - + # Sets the default migration template that is being used for the generation of the migration. + # Depending on command line arguments, the migration template and the table name instance + # variables are set up. def set_local_assigns! @migration_template = "migration.rb" case file_name @@ -56,6 +55,8 @@ module ActiveRecord attributes.select { |a| !a.reference? && a.has_index? } end + # A migration file name can only contain underscores (_), lowercase characters, + # and numbers 0-9. Any other file name will raise an IllegalMigrationNameError. def validate_file_name! unless file_name =~ /^[_a-z0-9]+$/ raise IllegalMigrationNameError.new(file_name) diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index 6577d56240..57eb5d0e18 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -59,6 +59,43 @@ class ActiveSchemaTest < ActiveRecord::TestCase assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree) end + def test_index_in_create + def (ActiveRecord::Base.connection).table_exists?(*); false; end + + %w(SPATIAL FULLTEXT UNIQUE).each do |type| + expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`) ) ENGINE=InnoDB" + actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t| + t.index :last_name, type: type + end + assert_equal expected, actual + end + + expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10)) ) ENGINE=InnoDB" + actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t| + t.index :last_name, length: 10, using: :btree + end + assert_equal expected, actual + end + + def test_index_in_bulk_change + def (ActiveRecord::Base.connection).table_exists?(*); true; end + def (ActiveRecord::Base.connection).index_name_exists?(*); false; end + + %w(SPATIAL FULLTEXT UNIQUE).each do |type| + expected = "ALTER TABLE `people` ADD #{type} INDEX `index_people_on_last_name` (`last_name`)" + actual = ActiveRecord::Base.connection.change_table(:people, bulk: true) do |t| + t.index :last_name, type: type + end + assert_equal expected, actual + end + + expected = "ALTER TABLE `peaple` ADD INDEX `index_peaple_on_last_name` USING btree (`last_name`(10)), ALGORITHM = COPY" + actual = ActiveRecord::Base.connection.change_table(:peaple, bulk: true) do |t| + t.index :last_name, length: 10, using: :btree, algorithm: :copy + end + assert_equal expected, actual + end + def test_drop_table assert_equal "DROP TABLE `people`", drop_table(:people) end diff --git a/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb index 340fc95503..345122b1ad 100644 --- a/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb +++ b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb @@ -1,5 +1,4 @@ require "cases/helper" -require 'models/person' class MysqlCaseSensitivityTest < ActiveRecord::TestCase class CollationTest < ActiveRecord::Base diff --git a/activerecord/test/cases/adapters/mysql/table_options_test.rb b/activerecord/test/cases/adapters/mysql/table_options_test.rb new file mode 100644 index 0000000000..0e5b0e8aec --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/table_options_test.rb @@ -0,0 +1,42 @@ +require "cases/helper" +require 'support/schema_dumping_helper' + +class MysqlTableOptionsTest < ActiveRecord::TestCase + include SchemaDumpingHelper + + def setup + @connection = ActiveRecord::Base.connection + end + + def teardown + @connection.drop_table "mysql_table_options", if_exists: true + end + + test "table options with ENGINE" do + @connection.create_table "mysql_table_options", force: true, options: "ENGINE=MyISAM" + output = dump_table_schema("mysql_table_options") + options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options] + assert_match %r{ENGINE=MyISAM}, options + end + + test "table options with ROW_FORMAT" do + @connection.create_table "mysql_table_options", force: true, options: "ROW_FORMAT=REDUNDANT" + output = dump_table_schema("mysql_table_options") + options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options] + assert_match %r{ROW_FORMAT=REDUNDANT}, options + end + + test "table options with CHARSET" do + @connection.create_table "mysql_table_options", force: true, options: "CHARSET=utf8mb4" + output = dump_table_schema("mysql_table_options") + options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options] + assert_match %r{CHARSET=utf8mb4}, options + end + + test "table options with COLLATE" do + @connection.create_table "mysql_table_options", force: true, options: "COLLATE=utf8mb4_bin" + output = dump_table_schema("mysql_table_options") + options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options] + assert_match %r{COLLATE=utf8mb4_bin}, options + end +end diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index e87cd3886a..0ea556d4fa 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -59,6 +59,43 @@ class ActiveSchemaTest < ActiveRecord::TestCase assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree) end + def test_index_in_create + def (ActiveRecord::Base.connection).table_exists?(*); false; end + + %w(SPATIAL FULLTEXT UNIQUE).each do |type| + expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`) ) ENGINE=InnoDB" + actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t| + t.index :last_name, type: type + end + assert_equal expected, actual + end + + expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10)) ) ENGINE=InnoDB" + actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t| + t.index :last_name, length: 10, using: :btree + end + assert_equal expected, actual + end + + def test_index_in_bulk_change + def (ActiveRecord::Base.connection).table_exists?(*); true; end + def (ActiveRecord::Base.connection).index_name_exists?(*); false; end + + %w(SPATIAL FULLTEXT UNIQUE).each do |type| + expected = "ALTER TABLE `people` ADD #{type} INDEX `index_people_on_last_name` (`last_name`)" + actual = ActiveRecord::Base.connection.change_table(:people, bulk: true) do |t| + t.index :last_name, type: type + end + assert_equal expected, actual + end + + expected = "ALTER TABLE `peaple` ADD INDEX `index_peaple_on_last_name` USING btree (`last_name`(10)), ALGORITHM = COPY" + actual = ActiveRecord::Base.connection.change_table(:peaple, bulk: true) do |t| + t.index :last_name, length: 10, using: :btree, algorithm: :copy + end + assert_equal expected, actual + end + def test_drop_table assert_equal "DROP TABLE `people`", drop_table(:people) end diff --git a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb index 09bebf3071..ccf3d84a44 100644 --- a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb +++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb @@ -1,5 +1,4 @@ require "cases/helper" -require 'models/person' class Mysql2CaseSensitivityTest < ActiveRecord::TestCase class CollationTest < ActiveRecord::Base diff --git a/activerecord/test/cases/adapters/mysql2/table_options_test.rb b/activerecord/test/cases/adapters/mysql2/table_options_test.rb new file mode 100644 index 0000000000..0e5b0e8aec --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/table_options_test.rb @@ -0,0 +1,42 @@ +require "cases/helper" +require 'support/schema_dumping_helper' + +class MysqlTableOptionsTest < ActiveRecord::TestCase + include SchemaDumpingHelper + + def setup + @connection = ActiveRecord::Base.connection + end + + def teardown + @connection.drop_table "mysql_table_options", if_exists: true + end + + test "table options with ENGINE" do + @connection.create_table "mysql_table_options", force: true, options: "ENGINE=MyISAM" + output = dump_table_schema("mysql_table_options") + options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options] + assert_match %r{ENGINE=MyISAM}, options + end + + test "table options with ROW_FORMAT" do + @connection.create_table "mysql_table_options", force: true, options: "ROW_FORMAT=REDUNDANT" + output = dump_table_schema("mysql_table_options") + options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options] + assert_match %r{ROW_FORMAT=REDUNDANT}, options + end + + test "table options with CHARSET" do + @connection.create_table "mysql_table_options", force: true, options: "CHARSET=utf8mb4" + output = dump_table_schema("mysql_table_options") + options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options] + assert_match %r{CHARSET=utf8mb4}, options + end + + test "table options with COLLATE" do + @connection.create_table "mysql_table_options", force: true, options: "COLLATE=utf8mb4_bin" + output = dump_table_schema("mysql_table_options") + options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options] + assert_match %r{COLLATE=utf8mb4_bin}, options + end +end diff --git a/activerecord/test/cases/adapters/postgresql/collation_test.rb b/activerecord/test/cases/adapters/postgresql/collation_test.rb new file mode 100644 index 0000000000..17ef5f304c --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/collation_test.rb @@ -0,0 +1,53 @@ +require "cases/helper" +require 'support/schema_dumping_helper' + +class PostgresqlCollationTest < ActiveRecord::TestCase + include SchemaDumpingHelper + + def setup + @connection = ActiveRecord::Base.connection + @connection.create_table :postgresql_collations, force: true do |t| + t.string :string_c, collation: 'C' + t.text :text_posix, collation: 'POSIX' + end + end + + def teardown + @connection.drop_table :postgresql_collations, if_exists: true + end + + test "string column with collation" do + column = @connection.columns(:postgresql_collations).find { |c| c.name == 'string_c' } + assert_equal :string, column.type + assert_equal 'C', column.collation + end + + test "text column with collation" do + column = @connection.columns(:postgresql_collations).find { |c| c.name == 'text_posix' } + assert_equal :text, column.type + assert_equal 'POSIX', column.collation + end + + test "add column with collation" do + @connection.add_column :postgresql_collations, :title, :string, collation: 'C' + + column = @connection.columns(:postgresql_collations).find { |c| c.name == 'title' } + assert_equal :string, column.type + assert_equal 'C', column.collation + end + + test "change column with collation" do + @connection.add_column :postgresql_collations, :description, :string + @connection.change_column :postgresql_collations, :description, :text, collation: 'POSIX' + + column = @connection.columns(:postgresql_collations).find { |c| c.name == 'description' } + assert_equal :text, column.type + assert_equal 'POSIX', column.collation + end + + test "schema dump includes collation" do + output = dump_table_schema("postgresql_collations") + assert_match %r{t.string\s+"string_c",\s+collation: "C"$}, output + assert_match %r{t.text\s+"text_posix",\s+collation: "POSIX"$}, output + end +end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 14cdf37f46..2c4e2a875c 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -2131,11 +2131,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase car = Car.create! original_child = FailedBulb.create!(car: car) - assert_raise(ActiveRecord::RecordNotDestroyed) do + error = assert_raise(ActiveRecord::RecordNotDestroyed) do car.failed_bulbs = [FailedBulb.create!] end assert_equal [original_child], car.reload.failed_bulbs + assert_equal "Failed to destroy the record", error.message end test 'updates counter cache when default scope is given' do diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 9734ea2217..190cef55c4 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -1040,14 +1040,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end end - def test_save_should_not_raise_exception_when_join_record_has_errors - repair_validations(Categorization) do - Categorization.validate { |r| r.errors[:base] << 'Invalid Categorization' } - c = Category.create(:name => 'Fishing', :authors => [Author.first]) - c.save - end - end - def test_assign_array_to_new_record_builds_join_records c = Category.new(:name => 'Fishing', :authors => [Author.first]) assert_equal 1, c.categorizations.size @@ -1072,11 +1064,11 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end end - def test_create_bang_returns_falsy_when_join_record_has_errors + def test_save_returns_falsy_when_join_record_has_errors repair_validations(Categorization) do Categorization.validate { |r| r.errors[:base] << 'Invalid Categorization' } c = Category.new(:name => 'Fishing', :authors => [Author.first]) - assert !c.save + assert_not c.save end end diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb index 3ae4a6eade..73ac30e547 100644 --- a/activerecord/test/cases/callbacks_test.rb +++ b/activerecord/test/cases/callbacks_test.rb @@ -451,6 +451,7 @@ class CallbacksTest < ActiveRecord::TestCase assert !david.save exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! } assert_equal exc.record, david + assert_equal "Failed to save the record", exc.message end david = ImmutableDeveloper.find(1) @@ -494,6 +495,7 @@ class CallbacksTest < ActiveRecord::TestCase assert !david.destroy exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! } assert_equal exc.record, david + assert_equal "Failed to destroy the record", exc.message end assert_not_nil ImmutableDeveloper.find_by_id(1) diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index aa50efc979..f5928814a3 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -341,6 +341,21 @@ module ActiveRecord handler.establish_connection anonymous, nil } end + + def test_pool_sets_connection_schema_cache + connection = pool.checkout + schema_cache = SchemaCache.new connection + schema_cache.add(:posts) + pool.schema_cache = schema_cache + + pool.with_connection do |conn| + assert_not_same pool.schema_cache, conn.schema_cache + assert_equal pool.schema_cache.size, conn.schema_cache.size + assert_same pool.schema_cache.columns(:posts), conn.schema_cache.columns(:posts) + end + + pool.checkin connection + end end end end diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index 3268555cb8..f67d85603a 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -7,15 +7,32 @@ require 'models/subscriber' require 'models/vegetables' require 'models/shop' +module InheritanceTestHelper + def with_store_full_sti_class(&block) + assign_store_full_sti_class true, &block + end + + def without_store_full_sti_class(&block) + assign_store_full_sti_class false, &block + end + + def assign_store_full_sti_class(flag) + old_store_full_sti_class = ActiveRecord::Base.store_full_sti_class + ActiveRecord::Base.store_full_sti_class = flag + yield + ensure + ActiveRecord::Base.store_full_sti_class = old_store_full_sti_class + end +end + class InheritanceTest < ActiveRecord::TestCase + include InheritanceTestHelper fixtures :companies, :projects, :subscribers, :accounts, :vegetables def test_class_with_store_full_sti_class_returns_full_name - old = ActiveRecord::Base.store_full_sti_class - ActiveRecord::Base.store_full_sti_class = true - assert_equal 'Namespaced::Company', Namespaced::Company.sti_name - ensure - ActiveRecord::Base.store_full_sti_class = old + with_store_full_sti_class do + assert_equal 'Namespaced::Company', Namespaced::Company.sti_name + end end def test_class_with_blank_sti_name @@ -33,39 +50,31 @@ class InheritanceTest < ActiveRecord::TestCase end def test_class_without_store_full_sti_class_returns_demodulized_name - old = ActiveRecord::Base.store_full_sti_class - ActiveRecord::Base.store_full_sti_class = false - assert_equal 'Company', Namespaced::Company.sti_name - ensure - ActiveRecord::Base.store_full_sti_class = old + without_store_full_sti_class do + assert_equal 'Company', Namespaced::Company.sti_name + end end def test_should_store_demodulized_class_name_with_store_full_sti_class_option_disabled - old = ActiveRecord::Base.store_full_sti_class - ActiveRecord::Base.store_full_sti_class = false - item = Namespaced::Company.new - assert_equal 'Company', item[:type] - ensure - ActiveRecord::Base.store_full_sti_class = old + without_store_full_sti_class do + item = Namespaced::Company.new + assert_equal 'Company', item[:type] + end end def test_should_store_full_class_name_with_store_full_sti_class_option_enabled - old = ActiveRecord::Base.store_full_sti_class - ActiveRecord::Base.store_full_sti_class = true - item = Namespaced::Company.new - assert_equal 'Namespaced::Company', item[:type] - ensure - ActiveRecord::Base.store_full_sti_class = old + with_store_full_sti_class do + item = Namespaced::Company.new + assert_equal 'Namespaced::Company', item[:type] + end end def test_different_namespace_subclass_should_load_correctly_with_store_full_sti_class_option - old = ActiveRecord::Base.store_full_sti_class - ActiveRecord::Base.store_full_sti_class = true - item = Namespaced::Company.create :name => "Wolverine 2" - assert_not_nil Company.find(item.id) - assert_not_nil Namespaced::Company.find(item.id) - ensure - ActiveRecord::Base.store_full_sti_class = old + with_store_full_sti_class do + item = Namespaced::Company.create name: "Wolverine 2" + assert_not_nil Company.find(item.id) + assert_not_nil Namespaced::Company.find(item.id) + end end def test_company_descends_from_active_record @@ -204,10 +213,28 @@ class InheritanceTest < ActiveRecord::TestCase assert_raise(ActiveRecord::SubclassNotFound) { Company.new(:type => 'Account') } end + def test_new_with_unrelated_namespaced_type + without_store_full_sti_class do + e = assert_raises ActiveRecord::SubclassNotFound do + Namespaced::Company.new(type: 'Firm') + end + + assert_equal "Invalid single-table inheritance type: Namespaced::Firm is not a subclass of Namespaced::Company", e.message + end + end + + def test_new_with_complex_inheritance assert_nothing_raised { Client.new(type: 'VerySpecialClient') } end + def test_new_without_storing_full_sti_class + without_store_full_sti_class do + item = Company.new(type: 'SpecialCo') + assert_instance_of Company::SpecialCo, item + end + end + def test_new_with_autoload_paths path = File.expand_path('../../models/autoloadable', __FILE__) ActiveSupport::Dependencies.autoload_paths << path @@ -331,6 +358,7 @@ class InheritanceTest < ActiveRecord::TestCase end class InheritanceComputeTypeTest < ActiveRecord::TestCase + include InheritanceTestHelper fixtures :companies def setup @@ -344,27 +372,26 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase end def test_instantiation_doesnt_try_to_require_corresponding_file - ActiveRecord::Base.store_full_sti_class = false - foo = Firm.first.clone - foo.type = 'FirmOnTheFly' - foo.save! + without_store_full_sti_class do + foo = Firm.first.clone + foo.type = 'FirmOnTheFly' + foo.save! - # Should fail without FirmOnTheFly in the type condition. - assert_raise(ActiveRecord::RecordNotFound) { Firm.find(foo.id) } + # Should fail without FirmOnTheFly in the type condition. + assert_raise(ActiveRecord::RecordNotFound) { Firm.find(foo.id) } - # Nest FirmOnTheFly in the test case where Dependencies won't see it. - self.class.const_set :FirmOnTheFly, Class.new(Firm) - assert_raise(ActiveRecord::SubclassNotFound) { Firm.find(foo.id) } + # Nest FirmOnTheFly in the test case where Dependencies won't see it. + self.class.const_set :FirmOnTheFly, Class.new(Firm) + assert_raise(ActiveRecord::SubclassNotFound) { Firm.find(foo.id) } - # Nest FirmOnTheFly in Firm where Dependencies will see it. - # This is analogous to nesting models in a migration. - Firm.const_set :FirmOnTheFly, Class.new(Firm) + # Nest FirmOnTheFly in Firm where Dependencies will see it. + # This is analogous to nesting models in a migration. + Firm.const_set :FirmOnTheFly, Class.new(Firm) - # And instantiate will find the existing constant rather than trying - # to require firm_on_the_fly. - assert_nothing_raised { assert_kind_of Firm::FirmOnTheFly, Firm.find(foo.id) } - ensure - ActiveRecord::Base.store_full_sti_class = true + # And instantiate will find the existing constant rather than trying + # to require firm_on_the_fly. + assert_nothing_raised { assert_kind_of Firm::FirmOnTheFly, Firm.find(foo.id) } + end end def test_sti_type_from_attributes_disabled_in_non_sti_class diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 63612e33af..e6f0fe6f75 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -168,24 +168,24 @@ class SchemaDumperTest < ActiveRecord::TestCase end def test_schema_dumps_index_columns_in_right_order - index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip + index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_index/).first.strip if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) - assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition + assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition else - assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index"', index_definition + assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index"', index_definition end end def test_schema_dumps_partial_indices - index_definition = standard_dump.split(/\n/).grep(/add_index.*company_partial_index/).first.strip + index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_partial_index/).first.strip if current_adapter?(:PostgreSQLAdapter) - assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition + assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition elsif current_adapter?(:MysqlAdapter, :Mysql2Adapter) - assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition + assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition elsif current_adapter?(:SQLite3Adapter) && ActiveRecord::Base.connection.supports_partial_index? - assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition + assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition else - assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index"', index_definition + assert_equal 't.index ["firm_id", "type"], name: "company_partial_index"', index_definition end end @@ -232,8 +232,8 @@ class SchemaDumperTest < ActiveRecord::TestCase def test_schema_dumps_index_type output = standard_dump - assert_match %r{add_index "key_tests", \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output - assert_match %r{add_index "key_tests", \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output + assert_match %r{t\.index \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output + assert_match %r{t\.index \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output end end diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb index f58535f044..8d69741a4a 100644 --- a/activerecord/test/cases/tasks/mysql_rake_test.rb +++ b/activerecord/test/cases/tasks/mysql_rake_test.rb @@ -283,12 +283,21 @@ module ActiveRecord def test_structure_dump_with_port_number filename = "awesome-file.sql" - Kernel.expects(:system).with("mysqldump", "--port", "10000", "--result-file", filename, "--no-data", "test-db").returns(true) + Kernel.expects(:system).with("mysqldump", "--port=10000", "--result-file", filename, "--no-data", "test-db").returns(true) ActiveRecord::Tasks::DatabaseTasks.structure_dump( @configuration.merge('port' => 10000), filename) end + + def test_structure_dump_with_ssl + filename = "awesome-file.sql" + Kernel.expects(:system).with("mysqldump", "--ssl-ca=ca.crt", "--result-file", filename, "--no-data", "test-db").returns(true) + + ActiveRecord::Tasks::DatabaseTasks.structure_dump( + @configuration.merge("sslca" => "ca.crt"), + filename) + end end class MySQLStructureLoadTest < ActiveRecord::TestCase diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 6961f8fd6f..67936e8e5d 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -26,6 +26,9 @@ class Company < AbstractCompany def private_method "I am Jack's innermost fears and aspirations" end + + class SpecialCo < Company + end end module Namespaced diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index 008503bc24..872fa595b4 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -1,5 +1,16 @@ ActiveRecord::Schema.define do + enable_extension!('uuid-ossp', ActiveRecord::Base.connection) + + create_table :uuid_parents, id: :uuid, force: true do |t| + t.string :name + end + + create_table :uuid_children, id: :uuid, force: true do |t| + t.string :name + t.uuid :uuid_parent_id + end + %w(postgresql_times postgresql_oids defaults postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent).each do |table_name| drop_table table_name, if_exists: true diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 7b42f8a4a5..66f8f1611d 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -6,20 +6,6 @@ ActiveRecord::Schema.define do end end - #put adapter specific setup here - case adapter_name - when "PostgreSQL" - enable_extension!('uuid-ossp', ActiveRecord::Base.connection) - create_table :uuid_parents, id: :uuid, force: true do |t| - t.string :name - end - create_table :uuid_children, id: :uuid, force: true do |t| - t.string :name - t.uuid :uuid_parent_id - end - end - - # ------------------------------------------------------------------- # # # # Please keep these create table statements in alphabetical order # |