diff options
Diffstat (limited to 'activerecord')
15 files changed, 94 insertions, 21 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 184e881b25..727ddd6bb7 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,11 @@ +* Make currency symbols optional for money column type in PostgreSQL + + *Joel Schneider* + +* Add support for beginless ranges, introduced in Ruby 2.7. + + *Josh Goodall* + * Add database_exists? method to connection adapters to check if a database exists. *Guilherme Mansur* diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 2af6d09b53..282c9fcf30 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -12,7 +12,6 @@ require "active_support/core_ext/hash/slice" require "active_support/core_ext/string/behavior" require "active_support/core_ext/kernel/singleton_class" require "active_support/core_ext/module/introspection" -require "active_support/core_ext/object/duplicable" require "active_support/core_ext/class/subclasses" require "active_record/attribute_decorators" require "active_record/define_callbacks" 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 e9ae8d159e..405fecb603 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -619,7 +619,11 @@ module ActiveRecord when ER_QUERY_INTERRUPTED QueryCanceled.new(message, sql: sql, binds: binds) else - super + if exception.is_a?(Mysql2::Error::TimeoutError) + ActiveRecord::AdapterTimeout.new(message, sql: sql, binds: binds) + else + super + end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb index 6434377b57..357493dfc0 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb @@ -26,9 +26,9 @@ module ActiveRecord value = value.sub(/^\((.+)\)$/, '-\1') # (4) case value - when /^-?\D+[\d,]+\.\d{2}$/ # (1) + when /^-?\D*[\d,]+\.\d{2}$/ # (1) value.gsub!(/[^-\d.]/, "") - when /^-?\D+[\d.]+,\d{2}$/ # (2) + when /^-?\D*[\d.]+,\d{2}$/ # (2) value.gsub!(/[^-\d,]/, "").sub!(/,/, ".") end diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index c8c06375a3..20cc987d6e 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -353,16 +353,24 @@ module ActiveRecord class IrreversibleOrderError < ActiveRecordError end + # Superclass for errors that have been aborted (either by client or server). + class QueryAborted < StatementInvalid + end + # LockWaitTimeout will be raised when lock wait timeout exceeded. class LockWaitTimeout < StatementInvalid end # StatementTimeout will be raised when statement timeout exceeded. - class StatementTimeout < StatementInvalid + class StatementTimeout < QueryAborted end # QueryCanceled will be raised when canceling statement due to user request. - class QueryCanceled < StatementInvalid + class QueryCanceled < QueryAborted + end + + # AdapterTimeout will be raised when database clients times out while waiting from the server. + class AdapterTimeout < QueryAborted end # UnknownAttributeReference is raised when an unknown and potentially unsafe diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 648fdd0dc4..4d9acc911b 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -112,7 +112,7 @@ db_namespace = namespace :db do end end - # desc 'Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x).' + desc "Rolls back the database one migration and re-migrates up (options: STEP=x, VERSION=x)." task redo: :load_config do raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty? @@ -128,7 +128,7 @@ db_namespace = namespace :db do # desc 'Resets your database using your migrations for the current environment' task reset: ["db:drop", "db:create", "db:migrate"] - # desc 'Runs the "up" for a given migration VERSION.' + desc 'Runs the "up" for a given migration VERSION.' task up: :load_config do ActiveRecord::Tasks::DatabaseTasks.raise_for_multi_db(command: "db:migrate:up") @@ -162,7 +162,7 @@ db_namespace = namespace :db do end end - # desc 'Runs the "down" for a given migration VERSION.' + desc 'Runs the "down" for a given migration VERSION.' task down: :load_config do ActiveRecord::Tasks::DatabaseTasks.raise_for_multi_db(command: "db:migrate:down") @@ -230,7 +230,7 @@ db_namespace = namespace :db do db_namespace["_dump"].invoke end - # desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.' + desc "Drops and recreates the database from db/schema.rb for the current environment and loads the seeds." task reset: [ "db:drop", "db:setup" ] # desc "Retrieves the charset for the current environment's database" @@ -297,10 +297,11 @@ db_namespace = namespace :db do ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config| ActiveRecord::Base.establish_connection(db_config.config) - ActiveRecord::Tasks::DatabaseTasks.migrate - # Skipped when no database - ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config.config, ActiveRecord::Base.schema_format, db_config.spec_name) + ActiveRecord::Tasks::DatabaseTasks.migrate + if ActiveRecord::Base.dump_schema_after_migration + ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config.config, ActiveRecord::Base.schema_format, db_config.spec_name) + end rescue ActiveRecord::NoDatabaseError ActiveRecord::Tasks::DatabaseTasks.create_current(db_config.env_name, db_config.spec_name) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 87a53966e4..6a181882ae 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -952,7 +952,7 @@ module ActiveRecord def optimizer_hints!(*args) # :nodoc: args.flatten! - self.optimizer_hints_values += args + self.optimizer_hints_values |= args self end diff --git a/activerecord/lib/arel/predications.rb b/activerecord/lib/arel/predications.rb index dece478615..895d394363 100644 --- a/activerecord/lib/arel/predications.rb +++ b/activerecord/lib/arel/predications.rb @@ -37,7 +37,7 @@ module Arel # :nodoc: all def between(other) if unboundable?(other.begin) == 1 || unboundable?(other.end) == -1 self.in([]) - elsif open_ended?(other.begin) + elsif other.begin.nil? || open_ended?(other.begin) if other.end.nil? || open_ended?(other.end) not_in([]) elsif other.exclude_end? @@ -85,7 +85,7 @@ Passing a range to `#in` is deprecated. Call `#between`, instead. def not_between(other) if unboundable?(other.begin) == 1 || unboundable?(other.end) == -1 not_in([]) - elsif open_ended?(other.begin) + elsif other.begin.nil? || open_ended?(other.begin) if other.end.nil? || open_ended?(other.end) self.in([]) elsif other.exclude_end? diff --git a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb index df84a40f63..cfc1823773 100644 --- a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb @@ -225,6 +225,21 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase end end + def test_read_timeout_exception + ActiveRecord::Base.establish_connection( + ActiveRecord::Base.configurations[:arunit].merge("read_timeout" => 1) + ) + + error = assert_raises(ActiveRecord::AdapterTimeout) do + ActiveRecord::Base.connection.execute("SELECT SLEEP(2)") + end + assert_kind_of ActiveRecord::QueryAborted, error + + assert_equal Mysql2::Error::TimeoutError, error.cause.class + ensure + ActiveRecord::Base.establish_connection :arunit + end + private def with_example_table(definition = "id int auto_increment primary key, number int, data varchar(255)", &block) super(@conn, "ex", definition, &block) diff --git a/activerecord/test/cases/adapters/mysql2/transaction_test.rb b/activerecord/test/cases/adapters/mysql2/transaction_test.rb index 52e283f247..2041cc308f 100644 --- a/activerecord/test/cases/adapters/mysql2/transaction_test.rb +++ b/activerecord/test/cases/adapters/mysql2/transaction_test.rb @@ -92,7 +92,7 @@ module ActiveRecord test "raises StatementTimeout when statement timeout exceeded" do skip unless ActiveRecord::Base.connection.show_variable("max_execution_time") - assert_raises(ActiveRecord::StatementTimeout) do + error = assert_raises(ActiveRecord::StatementTimeout) do s = Sample.create!(value: 1) latch1 = Concurrent::CountDownLatch.new latch2 = Concurrent::CountDownLatch.new @@ -117,10 +117,11 @@ module ActiveRecord thread.join end end + assert_kind_of ActiveRecord::QueryAborted, error end test "raises QueryCanceled when canceling statement due to user request" do - assert_raises(ActiveRecord::QueryCanceled) do + error = assert_raises(ActiveRecord::QueryCanceled) do s = Sample.create!(value: 1) latch = Concurrent::CountDownLatch.new @@ -144,6 +145,7 @@ module ActiveRecord thread.join end end + assert_kind_of ActiveRecord::QueryAborted, error end end end diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb index 1aa0348879..ff2ab22a80 100644 --- a/activerecord/test/cases/adapters/postgresql/money_test.rb +++ b/activerecord/test/cases/adapters/postgresql/money_test.rb @@ -54,8 +54,12 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase type = PostgresqlMoney.type_for_attribute("wealth") assert_equal(12345678.12, type.cast(+"$12,345,678.12")) assert_equal(12345678.12, type.cast(+"$12.345.678,12")) + assert_equal(12345678.12, type.cast(+"12,345,678.12")) + assert_equal(12345678.12, type.cast(+"12.345.678,12")) assert_equal(-1.15, type.cast(+"-$1.15")) assert_equal(-2.25, type.cast(+"($2.25)")) + assert_equal(-1.15, type.cast(+"-1.15")) + assert_equal(-2.25, type.cast(+"(2.25)")) end def test_schema_dumping diff --git a/activerecord/test/cases/arel/attributes/attribute_test.rb b/activerecord/test/cases/arel/attributes/attribute_test.rb index c7bd0a053b..7ebb90c6fd 100644 --- a/activerecord/test/cases/arel/attributes/attribute_test.rb +++ b/activerecord/test/cases/arel/attributes/attribute_test.rb @@ -638,6 +638,18 @@ module Arel ) end + if Gem::Version.new("2.7.0") <= Gem::Version.new(RUBY_VERSION) + it "can be constructed with a range implicitly starting at Infinity" do + attribute = Attribute.new nil, nil + node = attribute.between(eval("..0")) # eval for backwards compatibility + + node.must_equal Nodes::LessThanOrEqual.new( + attribute, + Nodes::Casted.new(0, attribute) + ) + end + end + if Gem::Version.new("2.6.0") <= Gem::Version.new(RUBY_VERSION) it "can be constructed with a range implicitly ending at Infinity" do attribute = Attribute.new nil, nil @@ -839,6 +851,18 @@ module Arel ) end + if Gem::Version.new("2.7.0") <= Gem::Version.new(RUBY_VERSION) + it "can be constructed with a range implicitly starting at Infinity" do + attribute = Attribute.new nil, nil + node = attribute.not_between(eval("..0")) # eval for backwards compatibility + + node.must_equal Nodes::GreaterThan.new( + attribute, + Nodes::Casted.new(0, attribute) + ) + end + end + if Gem::Version.new("2.6.0") <= Gem::Version.new(RUBY_VERSION) it "can be constructed with a range implicitly ending at Infinity" do attribute = Attribute.new nil, nil diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index e0743de94b..e74fb1a098 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -363,6 +363,13 @@ module ActiveRecord assert_match %r{/\*\+ BADHINT \*/}, post_with_hint.to_sql end + def test_does_not_duplicate_optimizer_hints_on_merge + escaped_table = Post.connection.quote_table_name("posts") + expected = "SELECT /*+ OMGHINT */ #{escaped_table}.* FROM #{escaped_table}" + query = Post.optimizer_hints("OMGHINT").merge(Post.optimizer_hints("OMGHINT")).to_sql + assert_equal expected, query + end + class EnsureRoundTripTypeCasting < ActiveRecord::Type::Value def type :string diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb index 2645776ab7..4dd8a4a82b 100644 --- a/activerecord/test/cases/validations/i18n_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_validation_test.rb @@ -51,7 +51,7 @@ class I18nValidationTest < ActiveRecord::TestCase test "validates_uniqueness_of on generated message #{name}" do Topic.validates_uniqueness_of :title, validation_options @topic.title = unique_topic.title - assert_called_with(@topic.errors, :generate_message, [:title, :taken, generate_message_options.merge(value: "unique!")]) do + assert_called_with(ActiveModel::Error, :generate_message, [:title, :taken, @topic, generate_message_options.merge(value: "unique!")]) do @topic.valid? @topic.errors.messages end @@ -61,7 +61,7 @@ class I18nValidationTest < ActiveRecord::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_associated on generated message #{name}" do Topic.validates_associated :replies, validation_options - assert_called_with(replied_topic.errors, :generate_message, [:replies, :invalid, generate_message_options.merge(value: replied_topic.replies)]) do + assert_called_with(ActiveModel::Error, :generate_message, [:replies, :invalid, replied_topic, generate_message_options.merge(value: replied_topic.replies)]) do replied_topic.save replied_topic.errors.messages end diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb index 7973219a79..6bab7a1eb9 100644 --- a/activerecord/test/models/ship.rb +++ b/activerecord/test/models/ship.rb @@ -27,7 +27,8 @@ class ShipWithoutNestedAttributes < ActiveRecord::Base has_many :prisoners, inverse_of: :ship, foreign_key: :ship_id has_many :parts, class_name: "ShipPart", foreign_key: :ship_id - validates :name, presence: true + validates :name, presence: true, if: -> { true } + validates :name, presence: true, if: -> { true } end class Prisoner < ActiveRecord::Base |