diff options
Diffstat (limited to 'activerecord/test/cases/adapters')
74 files changed, 1863 insertions, 1287 deletions
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index 67e1efde27..261fee13eb 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/connection_helper" @@ -7,7 +9,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase def setup ActiveRecord::Base.connection.singleton_class.class_eval do alias_method :execute_without_stub, :execute - def execute(sql, name = nil) return sql end + def execute(sql, name = nil) sql end end end @@ -66,14 +68,14 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase def (ActiveRecord::Base.connection).data_source_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" + expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`))" 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" + expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10)))" actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t| t.index :last_name, length: 10, using: :btree end @@ -104,9 +106,9 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase end def test_create_mysql_database_with_encoding - assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt) + assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8mb4`", create_database(:matt) assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, charset: "latin1") - assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, charset: :big5, collation: :big5_chinese_ci) + assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT COLLATE `utf8mb4_bin`", create_database(:matt_aimonetti, collation: "utf8mb4_bin") end def test_recreate_mysql_database_with_encoding @@ -146,8 +148,8 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase t.timestamps null: true end ActiveRecord::Base.connection.remove_timestamps :delete_me, null: true - assert !column_present?("delete_me", "updated_at", "datetime") - assert !column_present?("delete_me", "created_at", "datetime") + assert_not column_present?("delete_me", "updated_at", "datetime") + assert_not column_present?("delete_me", "created_at", "datetime") ensure ActiveRecord::Base.connection.drop_table :delete_me rescue nil end @@ -155,15 +157,19 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase end def test_indexes_in_create - ActiveRecord::Base.connection.stubs(:data_source_exists?).with(:temp).returns(false) - ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false) + assert_called_with( + ActiveRecord::Base.connection, + :data_source_exists?, + [:temp], + returns: false + ) do + expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`)) AS SELECT id, name, zip FROM a_really_complicated_query" + actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t| + t.index :zip + end - expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`)) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" - actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t| - t.index :zip + assert_equal expected, actual end - - assert_equal expected, actual end private diff --git a/activerecord/test/cases/adapters/mysql2/auto_increment_test.rb b/activerecord/test/cases/adapters/mysql2/auto_increment_test.rb new file mode 100644 index 0000000000..4c67633946 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/auto_increment_test.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "cases/helper" +require "support/schema_dumping_helper" + +class Mysql2AutoIncrementTest < ActiveRecord::Mysql2TestCase + include SchemaDumpingHelper + + def setup + @connection = ActiveRecord::Base.connection + end + + def teardown + @connection.drop_table :auto_increments, if_exists: true + end + + def test_auto_increment_without_primary_key + @connection.create_table :auto_increments, id: false, force: true do |t| + t.integer :id, null: false, auto_increment: true + t.index :id + end + output = dump_table_schema("auto_increments") + assert_match(/t\.integer\s+"id",\s+null: false,\s+auto_increment: true$/, output) + end + + def test_auto_increment_with_composite_primary_key + @connection.create_table :auto_increments, primary_key: [:id, :created_at], force: true do |t| + t.integer :id, null: false, auto_increment: true + t.datetime :created_at, null: false + end + output = dump_table_schema("auto_increments") + assert_match(/t\.integer\s+"id",\s+null: false,\s+auto_increment: true$/, output) + end +end diff --git a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb index 8f7c803a21..825bddfb73 100644 --- a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb +++ b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/topic" diff --git a/activerecord/test/cases/adapters/mysql2/boolean_test.rb b/activerecord/test/cases/adapters/mysql2/boolean_test.rb index 2fa39282fb..db09b30361 100644 --- a/activerecord/test/cases/adapters/mysql2/boolean_test.rb +++ b/activerecord/test/cases/adapters/mysql2/boolean_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase @@ -38,7 +40,7 @@ class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase assert_equal :string, string_column.type end - test "test type casting with emulated booleans" do + test "type casting with emulated booleans" do emulate_booleans true boolean = BooleanType.create!(archived: true, published: true) @@ -55,7 +57,7 @@ class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase assert_equal 0, @connection.type_cast(false) end - test "test type casting without emulated booleans" do + test "type casting without emulated booleans" do emulate_booleans false boolean = BooleanType.create!(archived: true, published: true) diff --git a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb index 50ba9ab831..aa870349be 100644 --- a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb +++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase @@ -12,8 +14,8 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase end def test_case_sensitive - assert !CollationTest.columns_hash["string_ci_column"].case_sensitive? - assert CollationTest.columns_hash["string_cs_column"].case_sensitive? + assert_not_predicate CollationTest.columns_hash["string_ci_column"], :case_sensitive? + assert_predicate CollationTest.columns_hash["string_cs_column"], :case_sensitive? end def test_case_insensitive_comparison_for_ci_column diff --git a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb index 8826ad7fd1..d0c57de65d 100644 --- a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb +++ b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/schema_dumping_helper" @@ -48,7 +50,7 @@ class Mysql2CharsetCollationTest < ActiveRecord::Mysql2TestCase test "schema dump includes collation" do output = dump_table_schema("charset_collations") - assert_match %r{t.string\s+"string_ascii_bin",\s+collation: "ascii_bin"$}, output - assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+collation: "ucs2_unicode_ci"$}, output + assert_match %r{t\.string\s+"string_ascii_bin",\s+collation: "ascii_bin"$}, output + assert_match %r{t\.text\s+"text_ucs2_unicode_ci",\s+collation: "ucs2_unicode_ci"$}, output end end diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index a2faf43b0d..3103589186 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/connection_helper" @@ -38,41 +40,29 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase end def test_no_automatic_reconnection_after_timeout - assert @connection.active? + assert_predicate @connection, :active? @connection.update("set @@wait_timeout=1") sleep 2 - assert !@connection.active? + assert_not_predicate @connection, :active? ensure # Repair all fixture connections so other tests won't break. @fixture_connections.each(&:verify!) end def test_successful_reconnection_after_timeout_with_manual_reconnect - assert @connection.active? + assert_predicate @connection, :active? @connection.update("set @@wait_timeout=1") sleep 2 @connection.reconnect! - assert @connection.active? + assert_predicate @connection, :active? end def test_successful_reconnection_after_timeout_with_verify - assert @connection.active? + assert_predicate @connection, :active? @connection.update("set @@wait_timeout=1") sleep 2 @connection.verify! - assert @connection.active? - end - - def test_verify_with_args_is_deprecated - assert_deprecated do - @connection.verify!(option: true) - end - assert_deprecated do - @connection.verify!([]) - end - assert_deprecated do - @connection.verify!({}) - end + assert_predicate @connection, :active? end def test_execute_after_disconnect @@ -114,8 +104,8 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase end def test_mysql_connection_collation_is_configured - assert_equal "utf8_unicode_ci", @connection.show_variable("collation_connection") - assert_equal "utf8_general_ci", ARUnit2Model.connection.show_variable("collation_connection") + assert_equal "utf8mb4_unicode_ci", @connection.show_variable("collation_connection") + assert_equal "utf8mb4_general_ci", ARUnit2Model.connection.show_variable("collation_connection") end def test_mysql_default_in_strict_mode @@ -180,14 +170,16 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase end def test_logs_name_show_variable + ActiveRecord::Base.connection.materialize_transactions + @subscriber.logged.clear @connection.show_variable "foo" assert_equal "SCHEMA", @subscriber.logged[0][1] end - def test_logs_name_rename_column_sql + def test_logs_name_rename_column_for_alter @connection.execute "CREATE TABLE `bar_baz` (`foo` varchar(255))" @subscriber.logged.clear - @connection.send(:rename_column_sql, "bar_baz", "foo", "foo2") + @connection.send(:rename_column_for_alter, "bar_baz", "foo", "foo2") assert_equal "SCHEMA", @subscriber.logged[0][1] ensure @connection.execute "DROP TABLE `bar_baz`" diff --git a/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb index c131a5169c..00a075e063 100644 --- a/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb +++ b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" class Mysql2DatetimePrecisionQuotingTest < ActiveRecord::Mysql2TestCase @@ -43,9 +45,10 @@ class Mysql2DatetimePrecisionQuotingTest < ActiveRecord::Mysql2TestCase end def stub_version(full_version_string) - @connection.stubs(:full_version).returns(full_version_string) - @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version) - yield + @connection.stub(:full_version, full_version_string) do + @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version) + yield + end ensure @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version) end diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb index 7ad3e3ca2d..832f5d61d1 100644 --- a/activerecord/test/cases/adapters/mysql2/enum_test.rb +++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" class Mysql2EnumTest < ActiveRecord::Mysql2TestCase @@ -11,11 +13,11 @@ class Mysql2EnumTest < ActiveRecord::Mysql2TestCase def test_should_not_be_unsigned column = EnumTest.columns_hash["enum_column"] - assert_not column.unsigned? + assert_not_predicate column, :unsigned? end def test_should_not_be_bigint column = EnumTest.columns_hash["enum_column"] - assert_not column.bigint? + assert_not_predicate column, :bigint? end end diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb index 7916921e5a..b8e778f0b0 100644 --- a/activerecord/test/cases/adapters/mysql2/explain_test.rb +++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb @@ -1,21 +1,23 @@ +# frozen_string_literal: true + require "cases/helper" -require "models/developer" -require "models/computer" +require "models/author" +require "models/post" class Mysql2ExplainTest < ActiveRecord::Mysql2TestCase - fixtures :developers + fixtures :authors def test_explain_for_one_query - explain = Developer.where(id: 1).explain - assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain - assert_match %r(developers |.* const), explain + explain = Author.where(id: 1).explain + assert_match %(EXPLAIN for: SELECT `authors`.* FROM `authors` WHERE `authors`.`id` = 1), explain + assert_match %r(authors |.* const), explain end def test_explain_with_eager_loading - explain = Developer.where(id: 1).includes(:audit_logs).explain - assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain - assert_match %r(developers |.* const), explain - assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` = 1), explain - assert_match %r(audit_logs |.* ALL), explain + explain = Author.where(id: 1).includes(:posts).explain + assert_match %(EXPLAIN for: SELECT `authors`.* FROM `authors` WHERE `authors`.`id` = 1), explain + assert_match %r(authors |.* const), explain + assert_match %(EXPLAIN for: SELECT `posts`.* FROM `posts` WHERE `posts`.`author_id` = 1), explain + assert_match %r(posts |.* ALL), explain end end diff --git a/activerecord/test/cases/adapters/mysql2/json_test.rb b/activerecord/test/cases/adapters/mysql2/json_test.rb index 6954006003..de78ba91f5 100644 --- a/activerecord/test/cases/adapters/mysql2/json_test.rb +++ b/activerecord/test/cases/adapters/mysql2/json_test.rb @@ -1,195 +1,24 @@ +# frozen_string_literal: true + require "cases/helper" -require "support/schema_dumping_helper" +require "cases/json_shared_test_cases" if ActiveRecord::Base.connection.supports_json? class Mysql2JSONTest < ActiveRecord::Mysql2TestCase - include SchemaDumpingHelper + include JSONSharedTestCases self.use_transactional_tests = false - class JsonDataType < ActiveRecord::Base - self.table_name = "json_data_type" - - store_accessor :settings, :resolution - end - def setup - @connection = ActiveRecord::Base.connection - begin - @connection.create_table("json_data_type") do |t| - t.json "payload" - t.json "settings" - end + super + @connection.create_table("json_data_type") do |t| + t.json "payload" + t.json "settings" end end - def teardown - @connection.drop_table :json_data_type, if_exists: true - JsonDataType.reset_column_information - end - - def test_column - column = JsonDataType.columns_hash["payload"] - assert_equal :json, column.type - assert_equal "json", column.sql_type - - type = JsonDataType.type_for_attribute("payload") - assert_not type.binary? - end - - def test_change_table_supports_json - @connection.change_table("json_data_type") do |t| - t.json "users" + private + def column_type + :json end - JsonDataType.reset_column_information - column = JsonDataType.columns_hash["users"] - assert_equal :json, column.type - end - - def test_schema_dumping - output = dump_table_schema("json_data_type") - assert_match(/t\.json\s+"settings"/, output) - end - - def test_cast_value_on_write - x = JsonDataType.new payload: { "string" => "foo", :symbol => :bar } - assert_equal({ "string" => "foo", :symbol => :bar }, x.payload_before_type_cast) - assert_equal({ "string" => "foo", "symbol" => "bar" }, x.payload) - x.save - assert_equal({ "string" => "foo", "symbol" => "bar" }, x.reload.payload) - end - - def test_type_cast_json - type = JsonDataType.type_for_attribute("payload") - - data = "{\"a_key\":\"a_value\"}" - hash = type.deserialize(data) - assert_equal({ "a_key" => "a_value" }, hash) - assert_equal({ "a_key" => "a_value" }, type.deserialize(data)) - - assert_equal({}, type.deserialize("{}")) - assert_equal({ "key" => nil }, type.deserialize('{"key": null}')) - assert_equal({ "c" => "}", '"a"' => 'b "a b' }, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"}))) - end - - def test_rewrite - @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')" - x = JsonDataType.first - x.payload = { '"a\'' => "b" } - assert x.save! - end - - def test_select - @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')" - x = JsonDataType.first - assert_equal({ "k" => "v" }, x.payload) - end - - def test_select_multikey - @connection.execute %q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')| - x = JsonDataType.first - assert_equal({ "k1" => "v1", "k2" => "v2", "k3" => [1, 2, 3] }, x.payload) - end - - def test_null_json - @connection.execute "insert into json_data_type (payload) VALUES(null)" - x = JsonDataType.first - assert_nil(x.payload) - end - - def test_select_array_json_value - @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| - x = JsonDataType.first - assert_equal(["v0", { "k1" => "v1" }], x.payload) - end - - def test_select_nil_json_after_create - json = JsonDataType.create(payload: nil) - x = JsonDataType.where(payload: nil).first - assert_equal(json, x) - end - - def test_select_nil_json_after_update - json = JsonDataType.create(payload: "foo") - x = JsonDataType.where(payload: nil).first - assert_nil(x) - - json.update_attributes payload: nil - x = JsonDataType.where(payload: nil).first - assert_equal(json.reload, x) - end - - def test_rewrite_array_json_value - @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| - x = JsonDataType.first - x.payload = ["v1", { "k2" => "v2" }, "v3"] - assert x.save! - end - - def test_with_store_accessors - x = JsonDataType.new(resolution: "320×480") - assert_equal "320×480", x.resolution - - x.save! - x = JsonDataType.first - assert_equal "320×480", x.resolution - - x.resolution = "640×1136" - x.save! - - x = JsonDataType.first - assert_equal "640×1136", x.resolution - end - - def test_duplication_with_store_accessors - x = JsonDataType.new(resolution: "320×480") - assert_equal "320×480", x.resolution - - y = x.dup - assert_equal "320×480", y.resolution - end - - def test_yaml_round_trip_with_store_accessors - x = JsonDataType.new(resolution: "320×480") - assert_equal "320×480", x.resolution - - y = YAML.load(YAML.dump(x)) - assert_equal "320×480", y.resolution - end - - def test_changes_in_place - json = JsonDataType.new - assert_not json.changed? - - json.payload = { "one" => "two" } - assert json.changed? - assert json.payload_changed? - - json.save! - assert_not json.changed? - - json.payload["three"] = "four" - assert json.payload_changed? - - json.save! - json.reload - - assert_equal({ "one" => "two", "three" => "four" }, json.payload) - assert_not json.changed? - end - - def test_assigning_string_literal - json = JsonDataType.create(payload: "foo") - assert_equal "foo", json.payload - end - - def test_assigning_number - json = JsonDataType.create(payload: 1.234) - assert_equal 1.234, json.payload - end - - def test_assigning_boolean - json = JsonDataType.create(payload: true) - assert_equal true, json.payload - end end end diff --git a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb index aab3dcb724..0719baaa23 100644 --- a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/ddl_helper" @@ -17,42 +19,31 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase end end - def test_valid_column - with_example_table do - column = @conn.columns("ex").find { |col| col.name == "id" } - assert @conn.valid_type?(column.type) - end - end - - def test_invalid_column - assert_not @conn.valid_type?(:foobar) - end - def test_columns_for_distinct_zero_orders assert_equal "posts.id", @conn.columns_for_distinct("posts.id", []) end def test_columns_for_distinct_one_order - assert_equal "posts.id, posts.created_at AS alias_0", + assert_equal "posts.created_at AS alias_0, posts.id", @conn.columns_for_distinct("posts.id", ["posts.created_at desc"]) end def test_columns_for_distinct_few_orders - assert_equal "posts.id, posts.created_at AS alias_0, posts.position AS alias_1", + assert_equal "posts.created_at AS alias_0, posts.position AS alias_1, posts.id", @conn.columns_for_distinct("posts.id", ["posts.created_at desc", "posts.position asc"]) end def test_columns_for_distinct_with_case assert_equal( - "posts.id, CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END AS alias_0", + "CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END AS alias_0, posts.id", @conn.columns_for_distinct("posts.id", ["CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END"]) ) end def test_columns_for_distinct_blank_not_nil_orders - assert_equal "posts.id, posts.created_at AS alias_0", + assert_equal "posts.created_at AS alias_0, posts.id", @conn.columns_for_distinct("posts.id", ["posts.created_at desc", "", " "]) end @@ -61,7 +52,7 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase def order.to_sql "posts.created_at desc" end - assert_equal "posts.id, posts.created_at AS alias_0", + assert_equal "posts.created_at AS alias_0, posts.id", @conn.columns_for_distinct("posts.id", [order]) end diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb deleted file mode 100644 index 2c778b1150..0000000000 --- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb +++ /dev/null @@ -1,151 +0,0 @@ -require "cases/helper" - -# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with -# reserved word names (ie: group, order, values, etc...) -class Mysql2ReservedWordTest < ActiveRecord::Mysql2TestCase - class Group < ActiveRecord::Base - Group.table_name = "group" - belongs_to :select - has_one :values - end - - class Select < ActiveRecord::Base - Select.table_name = "select" - has_many :groups - end - - class Values < ActiveRecord::Base - Values.table_name = "values" - end - - class Distinct < ActiveRecord::Base - Distinct.table_name = "distinct" - has_and_belongs_to_many :selects - has_many :values, through: :groups - end - - def setup - @connection = ActiveRecord::Base.connection - - # we call execute directly here (and do similar below) because ActiveRecord::Base#create_table() - # will fail with these table names if these test cases fail - - create_tables_directly "group" => "id int auto_increment primary key, `order` varchar(255), select_id int", - "select" => "id int auto_increment primary key", - "values" => "id int auto_increment primary key, group_id int", - "distinct" => "id int auto_increment primary key", - "distinct_select" => "distinct_id int, select_id int" - end - - teardown do - drop_tables_directly ["group", "select", "values", "distinct", "distinct_select", "order"] - end - - # create tables with reserved-word names and columns - def test_create_tables - assert_nothing_raised { - @connection.create_table :order do |t| - t.column :group, :string - end - } - end - - # rename tables with reserved-word names - def test_rename_tables - assert_nothing_raised { @connection.rename_table(:group, :order) } - end - - # alter column with a reserved-word name in a table with a reserved-word name - def test_change_columns - assert_nothing_raised { @connection.change_column_default(:group, :order, "whatever") } - #the quoting here will reveal any double quoting issues in change_column's interaction with the column method in the adapter - assert_nothing_raised { @connection.change_column("group", "order", :Int, default: 0) } - assert_nothing_raised { @connection.rename_column(:group, :order, :values) } - end - - # introspect table with reserved word name - def test_introspect - assert_nothing_raised { @connection.columns(:group) } - assert_nothing_raised { @connection.indexes(:group) } - end - - #fixtures - self.use_instantiated_fixtures = true - self.use_transactional_tests = false - - #activerecord model class with reserved-word table name - def test_activerecord_model - create_test_fixtures :select, :distinct, :group, :values, :distinct_select - x = nil - assert_nothing_raised { x = Group.new } - x.order = "x" - assert_nothing_raised { x.save } - x.order = "y" - assert_nothing_raised { x.save } - assert_nothing_raised { Group.find_by_order("y") } - assert_nothing_raised { Group.find(1) } - end - - # has_one association with reserved-word table name - def test_has_one_associations - create_test_fixtures :select, :distinct, :group, :values, :distinct_select - v = nil - assert_nothing_raised { v = Group.find(1).values } - assert_equal 2, v.id - end - - # belongs_to association with reserved-word table name - def test_belongs_to_associations - create_test_fixtures :select, :distinct, :group, :values, :distinct_select - gs = nil - assert_nothing_raised { gs = Select.find(2).groups } - assert_equal gs.length, 2 - assert(gs.collect(&:id).sort == [2, 3]) - end - - # has_and_belongs_to_many with reserved-word table name - def test_has_and_belongs_to_many - create_test_fixtures :select, :distinct, :group, :values, :distinct_select - s = nil - assert_nothing_raised { s = Distinct.find(1).selects } - assert_equal s.length, 2 - assert(s.collect(&:id).sort == [1, 2]) - end - - # activerecord model introspection with reserved-word table and column names - def test_activerecord_introspection - assert_nothing_raised { Group.table_exists? } - assert_nothing_raised { Group.columns } - end - - # Calculations - def test_calculations_work_with_reserved_words - assert_nothing_raised { Group.count } - end - - def test_associations_work_with_reserved_words - assert_nothing_raised { Select.all.merge!(includes: [:groups]).to_a } - end - - #the following functions were added to DRY test cases - - private - # custom fixture loader, uses FixtureSet#create_fixtures and appends base_path to the current file's path - def create_test_fixtures(*fixture_names) - ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names) - end - - # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name - def drop_tables_directly(table_names, connection = @connection) - table_names.each do |name| - connection.drop_table name, if_exists: true - end - end - - # custom create table, uses execute on connection to create a table, note: escapes table_name, does NOT escape columns - def create_tables_directly(tables, connection = @connection) - tables.each do |table_name, column_properties| - connection.execute("CREATE TABLE `#{table_name}` ( #{column_properties} )") - end - end -end diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb index 605baa9905..d7d9a2d732 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb @@ -1,6 +1,10 @@ +# frozen_string_literal: true + require "cases/helper" class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase + self.use_transactional_tests = false + def test_renaming_index_on_foreign_key connection.add_index "engines", "car_id" connection.add_foreign_key :engines, :cars, name: "fk_engines_cars" @@ -18,7 +22,7 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase ActiveRecord::SchemaMigration.create_table - assert connection.column_exists?(table_name, :version, :string, collation: "utf8_general_ci") + assert connection.column_exists?(table_name, :version, :string) end end @@ -29,8 +33,10 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase ActiveRecord::InternalMetadata.create_table - assert connection.column_exists?(table_name, :key, :string, collation: "utf8_general_ci") + assert connection.column_exists?(table_name, :key, :string) end + ensure + ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment end private diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb index 1fad5585de..1283b0642c 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/post" require "models/comment" @@ -65,7 +67,7 @@ module ActiveRecord end def test_data_source_exists_wrong_schema - assert(!@connection.data_source_exists?("#{@db_name}.zomg"), "data_source should not exist") + assert_not(@connection.data_source_exists?("#{@db_name}.zomg"), "data_source should not exist") end def test_dump_indexes diff --git a/activerecord/test/cases/adapters/mysql2/sp_test.rb b/activerecord/test/cases/adapters/mysql2/sp_test.rb index 4182532535..7b6dce71e9 100644 --- a/activerecord/test/cases/adapters/mysql2/sp_test.rb +++ b/activerecord/test/cases/adapters/mysql2/sp_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/topic" require "models/reply" @@ -15,7 +17,7 @@ class Mysql2StoredProcedureTest < ActiveRecord::Mysql2TestCase # Test that MySQL allows multiple results for stored procedures # # In MySQL 5.6, CLIENT_MULTI_RESULTS is enabled by default. - # http://dev.mysql.com/doc/refman/5.6/en/call.html + # https://dev.mysql.com/doc/refman/5.6/en/call.html def test_multi_results rows = @connection.select_rows("CALL ten();") assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}" diff --git a/activerecord/test/cases/adapters/mysql2/sql_types_test.rb b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb index d6e7f29a5c..e10642cbb4 100644 --- a/activerecord/test/cases/adapters/mysql2/sql_types_test.rb +++ b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" class Mysql2SqlTypesTest < ActiveRecord::Mysql2TestCase diff --git a/activerecord/test/cases/adapters/mysql2/table_options_test.rb b/activerecord/test/cases/adapters/mysql2/table_options_test.rb index 61a8ce9bc0..1c92df940f 100644 --- a/activerecord/test/cases/adapters/mysql2/table_options_test.rb +++ b/activerecord/test/cases/adapters/mysql2/table_options_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/schema_dumping_helper" @@ -15,28 +17,103 @@ class Mysql2TableOptionsTest < ActiveRecord::Mysql2TestCase 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] + options = %r{create_table "mysql_table_options", 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] + options = %r{create_table "mysql_table_options", 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] + options = %r{create_table "mysql_table_options", 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] + options = %r{create_table "mysql_table_options", options: "(?<options>.*)"}.match(output)[:options] assert_match %r{COLLATE=utf8mb4_bin}, options end end + +class Mysql2DefaultEngineOptionSchemaDumpTest < ActiveRecord::Mysql2TestCase + include SchemaDumpingHelper + self.use_transactional_tests = false + + def setup + @verbose_was = ActiveRecord::Migration.verbose + ActiveRecord::Migration.verbose = false + end + + def teardown + ActiveRecord::Base.connection.drop_table "mysql_table_options", if_exists: true + ActiveRecord::Migration.verbose = @verbose_was + ActiveRecord::SchemaMigration.delete_all rescue nil + end + + test "schema dump includes ENGINE=InnoDB if not provided" do + ActiveRecord::Base.connection.create_table "mysql_table_options", force: true + + output = dump_table_schema("mysql_table_options") + options = %r{create_table "mysql_table_options", options: "(?<options>.*)"}.match(output)[:options] + assert_match %r{ENGINE=InnoDB}, options + end + + test "schema dump includes ENGINE=InnoDB in legacy migrations" do + migration = Class.new(ActiveRecord::Migration[5.1]) do + def migrate(x) + create_table "mysql_table_options", force: true + end + end.new + + ActiveRecord::Migrator.new(:up, [migration]).migrate + + output = dump_table_schema("mysql_table_options") + options = %r{create_table "mysql_table_options", options: "(?<options>.*)"}.match(output)[:options] + assert_match %r{ENGINE=InnoDB}, options + end +end + +class Mysql2DefaultEngineOptionSqlOutputTest < ActiveRecord::Mysql2TestCase + self.use_transactional_tests = false + + def setup + @logger_was = ActiveRecord::Base.logger + @log = StringIO.new + @verbose_was = ActiveRecord::Migration.verbose + ActiveRecord::Base.logger = ActiveSupport::Logger.new(@log) + ActiveRecord::Migration.verbose = false + end + + def teardown + ActiveRecord::Base.logger = @logger_was + ActiveRecord::Migration.verbose = @verbose_was + ActiveRecord::Base.connection.drop_table "mysql_table_options", if_exists: true + ActiveRecord::SchemaMigration.delete_all rescue nil + end + + test "new migrations do not contain default ENGINE=InnoDB option" do + ActiveRecord::Base.connection.create_table "mysql_table_options", force: true + + assert_no_match %r{ENGINE=InnoDB}, @log.string + end + + test "legacy migrations contain default ENGINE=InnoDB option" do + migration = Class.new(ActiveRecord::Migration[5.1]) do + def migrate(x) + create_table "mysql_table_options", force: true + end + end.new + + ActiveRecord::Migrator.new(:up, [migration]).migrate + + assert_match %r{ENGINE=InnoDB}, @log.string + end +end diff --git a/activerecord/test/cases/adapters/mysql2/transaction_test.rb b/activerecord/test/cases/adapters/mysql2/transaction_test.rb index 16101e38cb..52e283f247 100644 --- a/activerecord/test/cases/adapters/mysql2/transaction_test.rb +++ b/activerecord/test/cases/adapters/mysql2/transaction_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/connection_helper" @@ -11,6 +13,7 @@ module ActiveRecord setup do @abort, Thread.abort_on_exception = Thread.abort_on_exception, false + Thread.report_on_exception, @original_report_on_exception = false, Thread.report_on_exception @connection = ActiveRecord::Base.connection @connection.clear_cache! @@ -29,6 +32,7 @@ module ActiveRecord @connection.drop_table "samples", if_exists: true Thread.abort_on_exception = @abort + Thread.report_on_exception = @original_report_on_exception end test "raises Deadlocked when a deadlock is encountered" do @@ -42,7 +46,7 @@ module ActiveRecord Sample.transaction do s1.lock! barrier.wait - s2.update_attributes value: 1 + s2.update value: 1 end end @@ -50,7 +54,91 @@ module ActiveRecord Sample.transaction do s2.lock! barrier.wait - s1.update_attributes value: 2 + s1.update value: 2 + end + ensure + thread.join + end + end + end + + test "raises LockWaitTimeout when lock wait timeout exceeded" do + assert_raises(ActiveRecord::LockWaitTimeout) do + s = Sample.create!(value: 1) + latch1 = Concurrent::CountDownLatch.new + latch2 = Concurrent::CountDownLatch.new + + thread = Thread.new do + Sample.transaction do + Sample.lock.find(s.id) + latch1.count_down + latch2.wait + end + end + + begin + Sample.transaction do + latch1.wait + Sample.connection.execute("SET innodb_lock_wait_timeout = 1") + Sample.lock.find(s.id) + end + ensure + Sample.connection.execute("SET innodb_lock_wait_timeout = DEFAULT") + latch2.count_down + thread.join + end + end + end + + test "raises StatementTimeout when statement timeout exceeded" do + skip unless ActiveRecord::Base.connection.show_variable("max_execution_time") + assert_raises(ActiveRecord::StatementTimeout) do + s = Sample.create!(value: 1) + latch1 = Concurrent::CountDownLatch.new + latch2 = Concurrent::CountDownLatch.new + + thread = Thread.new do + Sample.transaction do + Sample.lock.find(s.id) + latch1.count_down + latch2.wait + end + end + + begin + Sample.transaction do + latch1.wait + Sample.connection.execute("SET max_execution_time = 1") + Sample.lock.find(s.id) + end + ensure + Sample.connection.execute("SET max_execution_time = DEFAULT") + latch2.count_down + thread.join + end + end + end + + test "raises QueryCanceled when canceling statement due to user request" do + assert_raises(ActiveRecord::QueryCanceled) do + s = Sample.create!(value: 1) + latch = Concurrent::CountDownLatch.new + + thread = Thread.new do + Sample.transaction do + Sample.lock.find(s.id) + latch.count_down + sleep(0.5) + conn = Sample.connection + pid = conn.query_value("SELECT id FROM information_schema.processlist WHERE info LIKE '% FOR UPDATE'") + conn.execute("KILL QUERY #{pid}") + end + end + + begin + Sample.transaction do + latch.wait + Sample.lock.find(s.id) end ensure thread.join diff --git a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb index a0823be143..97da96003d 100644 --- a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb +++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/schema_dumping_helper" @@ -52,15 +54,15 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase end @connection.columns("unsigned_types").select { |c| /^unsigned_/.match?(c.name) }.each do |column| - assert column.unsigned? + assert_predicate column, :unsigned? end end test "schema dump includes unsigned option" do schema = dump_table_schema "unsigned_types" - assert_match %r{t.integer\s+"unsigned_integer",\s+unsigned: true$}, schema - assert_match %r{t.bigint\s+"unsigned_bigint",\s+unsigned: true$}, schema - assert_match %r{t.float\s+"unsigned_float",\s+limit: 24,\s+unsigned: true$}, schema - assert_match %r{t.decimal\s+"unsigned_decimal",\s+precision: 10,\s+scale: 2,\s+unsigned: true$}, schema + assert_match %r{t\.integer\s+"unsigned_integer",\s+unsigned: true$}, schema + assert_match %r{t\.bigint\s+"unsigned_bigint",\s+unsigned: true$}, schema + assert_match %r{t\.float\s+"unsigned_float",\s+unsigned: true$}, schema + assert_match %r{t\.decimal\s+"unsigned_decimal",\s+precision: 10,\s+scale: 2,\s+unsigned: true$}, schema end end diff --git a/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb b/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb index 442a4fb7b5..8494acee3b 100644 --- a/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb +++ b/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/schema_dumping_helper" @@ -16,6 +18,7 @@ if ActiveRecord::Base.connection.supports_virtual_columns? t.string :name t.virtual :upper_name, type: :string, as: "UPPER(`name`)" t.virtual :name_length, type: :integer, as: "LENGTH(`name`)", stored: true + t.virtual :name_octet_length, type: :integer, as: "OCTET_LENGTH(`name`)", stored: true end VirtualColumn.create(name: "Rails") end @@ -52,8 +55,9 @@ if ActiveRecord::Base.connection.supports_virtual_columns? def test_schema_dumping output = dump_table_schema("virtual_columns") - assert_match(/t\.virtual\s+"upper_name",\s+type: :string,\s+as: "UPPER\(`name`\)"$/i, output) - assert_match(/t\.virtual\s+"name_length",\s+type: :integer,\s+as: "LENGTH\(`name`\)",\s+stored: true$/i, output) + assert_match(/t\.virtual\s+"upper_name",\s+type: :string,\s+as: "(?:UPPER|UCASE)\(`name`\)"$/i, output) + assert_match(/t\.virtual\s+"name_length",\s+type: :integer,\s+as: "(?:octet_length|length)\(`name`\)",\s+stored: true$/i, output) + assert_match(/t\.virtual\s+"name_octet_length",\s+type: :integer,\s+as: "(?:octet_length|length)\(`name`\)",\s+stored: true$/i, output) end end end diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb index b787de8453..afd422881b 100644 --- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb @@ -1,7 +1,11 @@ +# frozen_string_literal: true + require "cases/helper" class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase def setup + ActiveRecord::Base.connection.materialize_transactions + ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do def execute(sql, name = nil) sql end end @@ -57,6 +61,15 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase assert_equal expected, add_index(:people, "lower(last_name)", using: type, unique: true) end + expected = %(CREATE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name" bpchar_pattern_ops)) + assert_equal expected, add_index(:people, :last_name, using: :gist, opclass: { last_name: :bpchar_pattern_ops }) + + expected = %(CREATE INDEX "index_people_on_last_name_and_first_name" ON "people" ("last_name" DESC NULLS LAST, "first_name" ASC)) + assert_equal expected, add_index(:people, [:last_name, :first_name], order: { last_name: "DESC NULLS LAST", first_name: :asc }) + + expected = %(CREATE INDEX "index_people_on_last_name" ON "people" ("last_name" NULLS FIRST)) + assert_equal expected, add_index(:people, :last_name, order: "NULLS FIRST") + assert_raise ArgumentError do add_index(:people, :last_name, algorithm: :copy) end diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index c78c6178ff..42618c2ec3 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/schema_dumping_helper" @@ -37,12 +39,45 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase def test_column assert_equal :string, @column.type assert_equal "character varying(255)", @column.sql_type - assert @column.array? - assert_not @type.binary? + assert_predicate @column, :array? + assert_not_predicate @type, :binary? ratings_column = PgArray.columns_hash["ratings"] assert_equal :integer, ratings_column.type - assert ratings_column.array? + assert_predicate ratings_column, :array? + end + + def test_not_compatible_with_serialize_array + new_klass = Class.new(PgArray) do + serialize :tags, Array + end + assert_raises(ActiveRecord::AttributeMethods::Serialization::ColumnNotSerializableError) do + new_klass.new + end + end + + class MyTags + def initialize(tags); @tags = tags end + def to_a; @tags end + def self.load(tags); new(tags) end + def self.dump(object); object.to_a end + end + + def test_array_with_serialized_attributes + new_klass = Class.new(PgArray) do + serialize :tags, MyTags + end + + new_klass.create!(tags: MyTags.new(["one", "two"])) + record = new_klass.first + + assert_instance_of MyTags, record.tags + assert_equal ["one", "two"], record.tags.to_a + + record.tags = MyTags.new(["three", "four"]) + record.save! + + assert_equal ["three", "four"], record.reload.tags.to_a end def test_default @@ -74,7 +109,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase assert_equal :text, column.type assert_equal [], PgArray.column_defaults["snippets"] - assert column.array? + assert_predicate column, :array? end def test_change_column_cant_make_non_array_column_to_array @@ -191,6 +226,14 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase assert_equal(PgArray.last.tags, tag_values) end + def test_insert_fixtures + tag_values = ["val1", "val2", "val3_with_'_multiple_quote_'_chars"] + assert_deprecated do + @connection.insert_fixtures([{ "tags" => tag_values }], "pg_arrays") + end + assert_equal(PgArray.last.tags, tag_values) + end + def test_attribute_for_inspect_for_array_field record = PgArray.new { |a| a.ratings = (1..10).to_a } assert_equal("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]", record.attribute_for_inspect(:ratings)) @@ -214,7 +257,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase x = PgArray.create!(tags: tags) x.reload - refute x.changed? + assert_not_predicate x, :changed? end def test_quoting_non_standard_delimiters @@ -236,7 +279,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase x.reload assert_equal %w(one two three), x.tags - assert_not x.changed? + assert_not_predicate x, :changed? end def test_mutate_value_in_array @@ -247,7 +290,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase x.reload assert_equal [{ "a" => "c" }, { "b" => "b" }], x.hstores - assert_not x.changed? + assert_not_predicate x, :changed? end def test_datetime_with_timezone_awareness @@ -310,7 +353,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase assert e1.persisted?, "Saving e1" e2 = klass.create("tags" => ["black", "blue"]) - assert !e2.persisted?, "e2 shouldn't be valid" + assert_not e2.persisted?, "e2 shouldn't be valid" assert e2.errors[:tags].any?, "Should have errors for tags" assert_equal ["has already been taken"], e2.errors[:tags], "Should have uniqueness message for tags" end diff --git a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb index 7712e809a2..c8e728bbb6 100644 --- a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/connection_helper" require "support/schema_dumping_helper" @@ -27,20 +29,20 @@ class PostgresqlBitStringTest < ActiveRecord::PostgreSQLTestCase column = PostgresqlBitString.columns_hash["a_bit"] assert_equal :bit, column.type assert_equal "bit(8)", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlBitString.type_for_attribute("a_bit") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_bit_string_varying_column column = PostgresqlBitString.columns_hash["a_bit_varying"] assert_equal :bit_varying, column.type assert_equal "bit varying(4)", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlBitString.type_for_attribute("a_bit_varying") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_default diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb index 505c297cd4..64bb6906cd 100644 --- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/schema_dumping_helper" @@ -47,7 +49,7 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase end def test_type_cast_binary_value - data = "\u001F\x8B".force_encoding("BINARY") + data = "\u001F\x8B".dup.force_encoding("BINARY") assert_equal(data, @type.deserialize(data)) end @@ -73,7 +75,7 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase def test_write_value data = "\u001F" record = ByteaDataType.create(payload: data) - assert_not record.new_record? + assert_not_predicate record, :new_record? assert_equal(data, record.payload) end @@ -89,23 +91,24 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase Thread.new do other_conn = ActiveRecord::Base.connection other_conn.execute("SET standard_conforming_strings = off") + other_conn.execute("SET escape_string_warning = off") end.join test_via_to_sql end def test_write_binary - data = File.read(File.join(File.dirname(__FILE__), "..", "..", "..", "assets", "example.log")) + data = File.read(File.join(__dir__, "..", "..", "..", "assets", "example.log")) assert(data.size > 1) record = ByteaDataType.create(payload: data) - assert_not record.new_record? + assert_not_predicate record, :new_record? assert_equal(data, record.payload) assert_equal(data, ByteaDataType.where(id: record.id).first.payload) end def test_write_nil record = ByteaDataType.create(payload: nil) - assert_not record.new_record? + assert_not_predicate record, :new_record? assert_nil(record.payload) assert_nil(ByteaDataType.where(id: record.id).first.payload) end diff --git a/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb b/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb index 03b44feab6..305e033642 100644 --- a/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb +++ b/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" class PostgresqlCaseInsensitiveTest < ActiveRecord::PostgreSQLTestCase diff --git a/activerecord/test/cases/adapters/postgresql/change_schema_test.rb b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb index ea642069d2..6dba4f3e14 100644 --- a/activerecord/test/cases/adapters/postgresql/change_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" module ActiveRecord @@ -31,7 +33,7 @@ module ActiveRecord connection.change_column :strings, :somedate, :timestamp, array: true, cast_as: :timestamp column = connection.columns(:strings).find { |c| c.name == "somedate" } assert_equal :datetime, column.type - assert column.array? + assert_predicate column, :array? end end end diff --git a/activerecord/test/cases/adapters/postgresql/cidr_test.rb b/activerecord/test/cases/adapters/postgresql/cidr_test.rb index 52f2a0096c..f20958fbd2 100644 --- a/activerecord/test/cases/adapters/postgresql/cidr_test.rb +++ b/activerecord/test/cases/adapters/postgresql/cidr_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "ipaddr" diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb index ca95e4b626..9eb0b7d99c 100644 --- a/activerecord/test/cases/adapters/postgresql/citext_test.rb +++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb @@ -1,78 +1,78 @@ +# frozen_string_literal: true + require "cases/helper" require "support/schema_dumping_helper" -if ActiveRecord::Base.connection.supports_extensions? - class PostgresqlCitextTest < ActiveRecord::PostgreSQLTestCase - include SchemaDumpingHelper - class Citext < ActiveRecord::Base - self.table_name = "citexts" - end +class PostgresqlCitextTest < ActiveRecord::PostgreSQLTestCase + include SchemaDumpingHelper + class Citext < ActiveRecord::Base + self.table_name = "citexts" + end - def setup - @connection = ActiveRecord::Base.connection + def setup + @connection = ActiveRecord::Base.connection - enable_extension!("citext", @connection) + enable_extension!("citext", @connection) - @connection.create_table("citexts") do |t| - t.citext "cival" - end + @connection.create_table("citexts") do |t| + t.citext "cival" end + end - teardown do - @connection.drop_table "citexts", if_exists: true - disable_extension!("citext", @connection) - end + teardown do + @connection.drop_table "citexts", if_exists: true + disable_extension!("citext", @connection) + end - def test_citext_enabled - assert @connection.extension_enabled?("citext") - end + def test_citext_enabled + assert @connection.extension_enabled?("citext") + end - def test_column - column = Citext.columns_hash["cival"] - assert_equal :citext, column.type - assert_equal "citext", column.sql_type - assert_not column.array? + def test_column + column = Citext.columns_hash["cival"] + assert_equal :citext, column.type + assert_equal "citext", column.sql_type + assert_not_predicate column, :array? - type = Citext.type_for_attribute("cival") - assert_not type.binary? - end - - def test_change_table_supports_json - @connection.transaction do - @connection.change_table("citexts") do |t| - t.citext "username" - end - Citext.reset_column_information - column = Citext.columns_hash["username"] - assert_equal :citext, column.type + type = Citext.type_for_attribute("cival") + assert_not_predicate type, :binary? + end - raise ActiveRecord::Rollback # reset the schema change + def test_change_table_supports_json + @connection.transaction do + @connection.change_table("citexts") do |t| + t.citext "username" end - ensure Citext.reset_column_information + column = Citext.columns_hash["username"] + assert_equal :citext, column.type + + raise ActiveRecord::Rollback # reset the schema change end + ensure + Citext.reset_column_information + end - def test_write - x = Citext.new(cival: "Some CI Text") - x.save! - citext = Citext.first - assert_equal "Some CI Text", citext.cival + def test_write + x = Citext.new(cival: "Some CI Text") + x.save! + citext = Citext.first + assert_equal "Some CI Text", citext.cival - citext.cival = "Some NEW CI Text" - citext.save! + citext.cival = "Some NEW CI Text" + citext.save! - assert_equal "Some NEW CI Text", citext.reload.cival - end + assert_equal "Some NEW CI Text", citext.reload.cival + end - def test_select_case_insensitive - @connection.execute "insert into citexts (cival) values('Cased Text')" - x = Citext.where(cival: "cased text").first - assert_equal "Cased Text", x.cival - end + def test_select_case_insensitive + @connection.execute "insert into citexts (cival) values('Cased Text')" + x = Citext.where(cival: "cased text").first + assert_equal "Cased Text", x.cival + end - def test_schema_dump_with_shorthand - output = dump_table_schema("citexts") - assert_match %r[t\.citext "cival"], output - end + def test_schema_dump_with_shorthand + output = dump_table_schema("citexts") + assert_match %r[t\.citext "cival"], output end end diff --git a/activerecord/test/cases/adapters/postgresql/collation_test.rb b/activerecord/test/cases/adapters/postgresql/collation_test.rb index b39e298a5d..7468f4c4f8 100644 --- a/activerecord/test/cases/adapters/postgresql/collation_test.rb +++ b/activerecord/test/cases/adapters/postgresql/collation_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/schema_dumping_helper" @@ -47,7 +49,7 @@ class PostgresqlCollationTest < ActiveRecord::PostgreSQLTestCase 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 + 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/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb index 1da2a9e2ac..b0ce2694a3 100644 --- a/activerecord/test/cases/adapters/postgresql/composite_test.rb +++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/connection_helper" @@ -49,10 +51,10 @@ class PostgresqlCompositeTest < ActiveRecord::PostgreSQLTestCase column = PostgresqlComposite.columns_hash["address"] assert_nil column.type assert_equal "full_address", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlComposite.type_for_attribute("address") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_composite_mapping @@ -104,17 +106,17 @@ class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::PostgreSQLTestCase def setup super - @connection.type_map.register_type "full_address", FullAddressType.new + @connection.send(:type_map).register_type "full_address", FullAddressType.new end def test_column column = PostgresqlComposite.columns_hash["address"] assert_equal :full_address, column.type assert_equal "full_address", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlComposite.type_for_attribute("address") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_composite_mapping diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index c52d9e37cc..70aa189893 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/connection_helper" @@ -13,8 +15,9 @@ module ActiveRecord def setup super @subscriber = SQLSubscriber.new - @subscription = ActiveSupport::Notifications.subscribe("sql.active_record", @subscriber) @connection = ActiveRecord::Base.connection + @connection.materialize_transactions + @subscription = ActiveSupport::Notifications.subscribe("sql.active_record", @subscriber) end def teardown @@ -31,15 +34,21 @@ module ActiveRecord end def test_encoding - assert_not_nil @connection.encoding + assert_queries(1) do + assert_not_nil @connection.encoding + end end def test_collation - assert_not_nil @connection.collation + assert_queries(1) do + assert_not_nil @connection.collation + end end def test_ctype - assert_not_nil @connection.ctype + assert_queries(1) do + assert_not_nil @connection.ctype + end end def test_default_client_min_messages @@ -95,7 +104,7 @@ module ActiveRecord end def test_indexes_logs_name - assert_deprecated { @connection.indexes("items", "hello") } + @connection.indexes("items") assert_equal "SCHEMA", @subscriber.logged[0][1] end @@ -143,13 +152,13 @@ module ActiveRecord # When prompted, restart the PostgreSQL server with the # "-m fast" option or kill the individual connection assuming # you know the incantation to do that. - # To restart PostgreSQL 9.1 on OS X, installed via MacPorts, ... + # To restart PostgreSQL 9.1 on macOS, installed via MacPorts, ... # sudo su postgres -c "pg_ctl restart -D /opt/local/var/db/postgresql91/defaultdb/ -m fast" def test_reconnection_after_actual_disconnection_with_verify original_connection_pid = @connection.query("select pg_backend_pid()") # Sanity check. - assert @connection.active? + assert_predicate @connection, :active? if @connection.send(:postgresql_version) >= 90200 secondary_connection = ActiveRecord::Base.connection_pool.checkout @@ -168,7 +177,7 @@ module ActiveRecord @connection.verify! - assert @connection.active? + assert_predicate @connection, :active? # If we get no exception here, then either we re-connected successfully, or # we never actually got disconnected. @@ -212,6 +221,13 @@ module ActiveRecord end end + def test_set_session_timezone + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { timezone: "America/New_York" })) + assert_equal "America/New_York", ActiveRecord::Base.connection.query_value("SHOW TIME ZONE") + end + end + def test_get_and_release_advisory_lock lock_id = 5295901941911233559 list_advisory_locks = <<-SQL diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index 0725fde5ae..b7535d5c9a 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/ddl_helper" diff --git a/activerecord/test/cases/adapters/postgresql/date_test.rb b/activerecord/test/cases/adapters/postgresql/date_test.rb new file mode 100644 index 0000000000..a86abac2be --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/date_test.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require "cases/helper" +require "models/topic" + +class PostgresqlDateTest < ActiveRecord::PostgreSQLTestCase + def test_load_infinity_and_beyond + topic = Topic.find_by_sql("SELECT 'infinity'::date AS last_read").first + assert topic.last_read.infinite?, "timestamp should be infinite" + assert_operator topic.last_read, :>, 0 + + topic = Topic.find_by_sql("SELECT '-infinity'::date AS last_read").first + assert topic.last_read.infinite?, "timestamp should be infinite" + assert_operator topic.last_read, :<, 0 + end + + def test_save_infinity_and_beyond + topic = Topic.create!(last_read: 1.0 / 0.0) + assert_equal(1.0 / 0.0, topic.last_read) + + topic = Topic.create!(last_read: -1.0 / 0.0) + assert_equal(-1.0 / 0.0, topic.last_read) + end + + def test_bc_date + date = Date.new(0) - 1.week + topic = Topic.create!(last_read: date) + assert_equal date, Topic.find(topic.id).last_read + end + + def test_bc_date_leap_year + date = Time.utc(-4, 2, 29).to_date + topic = Topic.create!(last_read: date) + assert_equal date, Topic.find(topic.id).last_read + end + + def test_bc_date_year_zero + date = Time.utc(0, 4, 7).to_date + topic = Topic.create!(last_read: date) + assert_equal date, Topic.find(topic.id).last_read + end +end diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb index f1eb8adb15..eeaad94c27 100644 --- a/activerecord/test/cases/adapters/postgresql/domain_test.rb +++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/connection_helper" @@ -28,10 +30,10 @@ class PostgresqlDomainTest < ActiveRecord::PostgreSQLTestCase column = PostgresqlDomain.columns_hash["price"] assert_equal :decimal, column.type assert_equal "custom_money", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlDomain.type_for_attribute("price") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_domain_acts_like_basetype @@ -42,6 +44,6 @@ class PostgresqlDomainTest < ActiveRecord::PostgreSQLTestCase record.price = "34.15" record.save! - assert_equal BigDecimal.new("34.15"), record.reload.price + assert_equal BigDecimal("34.15"), record.reload.price end end diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb index 5e5a3158ba..6789ff63e7 100644 --- a/activerecord/test/cases/adapters/postgresql/enum_test.rb +++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/connection_helper" @@ -30,10 +32,10 @@ class PostgresqlEnumTest < ActiveRecord::PostgreSQLTestCase column = PostgresqlEnum.columns_hash["current_mood"] assert_equal :enum, column.type assert_equal "mood", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlEnum.type_for_attribute("current_mood") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_enum_defaults @@ -71,7 +73,7 @@ class PostgresqlEnumTest < ActiveRecord::PostgreSQLTestCase @connection.execute "INSERT INTO postgresql_enums VALUES (1, 'sad');" stderr_output = capture(:stderr) { PostgresqlEnum.first } - assert stderr_output.blank? + assert_predicate stderr_output, :blank? end def test_enum_type_cast diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb index 7493bce4fb..be525383e9 100644 --- a/activerecord/test/cases/adapters/postgresql/explain_test.rb +++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb @@ -1,20 +1,22 @@ +# frozen_string_literal: true + require "cases/helper" -require "models/developer" -require "models/computer" +require "models/author" +require "models/post" class PostgreSQLExplainTest < ActiveRecord::PostgreSQLTestCase - fixtures :developers + fixtures :authors def test_explain_for_one_query - explain = Developer.where(id: 1).explain - assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\$1 \[\["id", 1\]\]|1)), explain + explain = Author.where(id: 1).explain + assert_match %r(EXPLAIN for: SELECT "authors"\.\* FROM "authors" WHERE "authors"\."id" = (?:\$1 \[\["id", 1\]\]|1)), explain assert_match %(QUERY PLAN), explain end def test_explain_with_eager_loading - explain = Developer.where(id: 1).includes(:audit_logs).explain + explain = Author.where(id: 1).includes(:posts).explain assert_match %(QUERY PLAN), explain - assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\$1 \[\["id", 1\]\]|1)), explain - assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain + assert_match %r(EXPLAIN for: SELECT "authors"\.\* FROM "authors" WHERE "authors"\."id" = (?:\$1 \[\["id", 1\]\]|1)), explain + assert_match %r(EXPLAIN for: SELECT "posts"\.\* FROM "posts" WHERE "posts"\."author_id" = (?:\$1 \[\["author_id", 1\]\]|1)), explain end end diff --git a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb index b56c226763..df97ab11e7 100644 --- a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb +++ b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" class PostgresqlExtensionMigrationTest < ActiveRecord::PostgreSQLTestCase @@ -20,10 +22,6 @@ class PostgresqlExtensionMigrationTest < ActiveRecord::PostgreSQLTestCase @connection = ActiveRecord::Base.connection - unless @connection.supports_extensions? - return skip("no extension support") - end - @old_schema_migration_table_name = ActiveRecord::SchemaMigration.table_name @old_table_name_prefix = ActiveRecord::Base.table_name_prefix @old_table_name_suffix = ActiveRecord::Base.table_name_suffix diff --git a/activerecord/test/cases/adapters/postgresql/foreign_table_test.rb b/activerecord/test/cases/adapters/postgresql/foreign_table_test.rb new file mode 100644 index 0000000000..4fa315ad23 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/foreign_table_test.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +require "cases/helper" +require "models/professor" + +if ActiveRecord::Base.connection.supports_foreign_tables? + class ForeignTableTest < ActiveRecord::TestCase + self.use_transactional_tests = false + + class ForeignProfessor < ActiveRecord::Base + self.table_name = "foreign_professors" + end + + class ForeignProfessorWithPk < ForeignProfessor + self.primary_key = "id" + end + + def setup + @professor = Professor.create(name: "Nicola") + + @connection = ActiveRecord::Base.connection + enable_extension!("postgres_fdw", @connection) + + foreign_db_config = ARTest.connection_config["arunit2"] + @connection.execute <<-SQL + CREATE SERVER foreign_server + FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '#{foreign_db_config["database"]}') + SQL + + @connection.execute <<-SQL + CREATE USER MAPPING FOR CURRENT_USER + SERVER foreign_server + SQL + + @connection.execute <<-SQL + CREATE FOREIGN TABLE foreign_professors ( + id int, + name character varying NOT NULL + ) SERVER foreign_server OPTIONS ( + table_name 'professors' + ) + SQL + end + + def teardown + disable_extension!("postgres_fdw", @connection) + @connection.execute <<-SQL + DROP SERVER IF EXISTS foreign_server CASCADE + SQL + end + + def test_table_exists + table_name = ForeignProfessor.table_name + assert_not ActiveRecord::Base.connection.table_exists?(table_name) + end + + def test_foreign_tables_are_valid_data_sources + table_name = ForeignProfessor.table_name + assert @connection.data_source_exists?(table_name), "'#{table_name}' should be a data source" + end + + def test_foreign_tables + assert_equal ["foreign_professors"], @connection.foreign_tables + end + + def test_foreign_table_exists + assert @connection.foreign_table_exists?("foreign_professors") + assert @connection.foreign_table_exists?(:foreign_professors) + assert_not @connection.foreign_table_exists?("nonexistingtable") + assert_not @connection.foreign_table_exists?("'") + assert_not @connection.foreign_table_exists?(nil) + end + + def test_attribute_names + assert_equal ["id", "name"], ForeignProfessor.attribute_names + end + + def test_attributes + professor = ForeignProfessorWithPk.find(@professor.id) + assert_equal @professor.attributes, professor.attributes + end + + def test_does_not_have_a_primary_key + assert_nil ForeignProfessor.primary_key + end + + def test_insert_record + # Explicit `id` here to avoid complex configurations to implicitly work with remote table + ForeignProfessorWithPk.create!(id: 100, name: "Leonardo") + + professor = ForeignProfessorWithPk.last + assert_equal "Leonardo", professor.name + end + + def test_update_record + professor = ForeignProfessorWithPk.find(@professor.id) + professor.name = "Albert" + professor.save! + professor.reload + assert_equal "Albert", professor.name + end + + def test_delete_record + professor = ForeignProfessorWithPk.find(@professor.id) + assert_difference("ForeignProfessor.count", -1) { professor.destroy } + end + end +end diff --git a/activerecord/test/cases/adapters/postgresql/full_text_test.rb b/activerecord/test/cases/adapters/postgresql/full_text_test.rb index 5ddfe32007..95dee3bf44 100644 --- a/activerecord/test/cases/adapters/postgresql/full_text_test.rb +++ b/activerecord/test/cases/adapters/postgresql/full_text_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/schema_dumping_helper" @@ -20,10 +22,10 @@ class PostgresqlFullTextTest < ActiveRecord::PostgreSQLTestCase column = Tsvector.columns_hash["text_vector"] assert_equal :tsvector, column.type assert_equal "tsvector", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = Tsvector.type_for_attribute("text_vector") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_update_tsvector diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb index c1f3a4ae2c..8c6f046553 100644 --- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb +++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/connection_helper" require "support/schema_dumping_helper" @@ -37,10 +39,10 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase column = PostgresqlPoint.columns_hash["x"] assert_equal :point, column.type assert_equal "point", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlPoint.type_for_attribute("x") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_default @@ -77,7 +79,7 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase p.reload assert_equal ActiveRecord::Point.new(10.0, 25.0), p.x - assert_not p.changed? + assert_not_predicate p, :changed? end def test_array_assignment @@ -93,8 +95,6 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase end def test_empty_string_assignment - assert_nothing_raised { PostgresqlPoint.new(x: "") } - p = PostgresqlPoint.new(x: "") assert_nil p.x end @@ -117,10 +117,10 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase column = PostgresqlPoint.columns_hash["legacy_x"] assert_equal :point, column.type assert_equal "point", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlPoint.type_for_attribute("legacy_x") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_legacy_default @@ -157,7 +157,7 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase p.reload assert_equal [10.0, 25.0], p.legacy_x - assert_not p.changed? + assert_not_predicate p, :changed? end end diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index f9cce10fb8..4b061a9375 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -1,382 +1,378 @@ +# frozen_string_literal: true + require "cases/helper" require "support/schema_dumping_helper" -if ActiveRecord::Base.connection.supports_extensions? - class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase - include SchemaDumpingHelper - class Hstore < ActiveRecord::Base - self.table_name = "hstores" +class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase + include SchemaDumpingHelper + class Hstore < ActiveRecord::Base + self.table_name = "hstores" - store_accessor :settings, :language, :timezone - end + store_accessor :settings, :language, :timezone + end - class FakeParameters - def to_unsafe_h - { "hi" => "hi" } - end + class FakeParameters + def to_unsafe_h + { "hi" => "hi" } end + end - def setup - @connection = ActiveRecord::Base.connection - - unless @connection.extension_enabled?("hstore") - @connection.enable_extension "hstore" - @connection.commit_db_transaction - end + def setup + @connection = ActiveRecord::Base.connection - @connection.reconnect! + enable_extension!("hstore", @connection) - @connection.transaction do - @connection.create_table("hstores") do |t| - t.hstore "tags", default: "" - t.hstore "payload", array: true - t.hstore "settings" - end + @connection.transaction do + @connection.create_table("hstores") do |t| + t.hstore "tags", default: "" + t.hstore "payload", array: true + t.hstore "settings" end - Hstore.reset_column_information - @column = Hstore.columns_hash["tags"] - @type = Hstore.type_for_attribute("tags") - end - - teardown do - @connection.drop_table "hstores", if_exists: true end + Hstore.reset_column_information + @column = Hstore.columns_hash["tags"] + @type = Hstore.type_for_attribute("tags") + end - def test_hstore_included_in_extensions - assert @connection.respond_to?(:extensions), "connection should have a list of extensions" - assert_includes @connection.extensions, "hstore", "extension list should include hstore" - end + teardown do + @connection.drop_table "hstores", if_exists: true + disable_extension!("hstore", @connection) + end - def test_disable_enable_hstore - assert @connection.extension_enabled?("hstore") - @connection.disable_extension "hstore" - assert_not @connection.extension_enabled?("hstore") - @connection.enable_extension "hstore" - assert @connection.extension_enabled?("hstore") - ensure - # Restore column(s) dropped by `drop extension hstore cascade;` - load_schema - end + def test_hstore_included_in_extensions + assert_respond_to @connection, :extensions + assert_includes @connection.extensions, "hstore", "extension list should include hstore" + end - def test_column - assert_equal :hstore, @column.type - assert_equal "hstore", @column.sql_type - assert_not @column.array? + def test_disable_enable_hstore + assert @connection.extension_enabled?("hstore") + @connection.disable_extension "hstore" + assert_not @connection.extension_enabled?("hstore") + @connection.enable_extension "hstore" + assert @connection.extension_enabled?("hstore") + ensure + # Restore column(s) dropped by `drop extension hstore cascade;` + load_schema + end - assert_not @type.binary? - end + def test_column + assert_equal :hstore, @column.type + assert_equal "hstore", @column.sql_type + assert_not_predicate @column, :array? - def test_default - @connection.add_column "hstores", "permissions", :hstore, default: '"users"=>"read", "articles"=>"write"' - Hstore.reset_column_information + assert_not_predicate @type, :binary? + end - assert_equal({ "users" => "read", "articles" => "write" }, Hstore.column_defaults["permissions"]) - assert_equal({ "users" => "read", "articles" => "write" }, Hstore.new.permissions) - ensure - Hstore.reset_column_information - end + def test_default + @connection.add_column "hstores", "permissions", :hstore, default: '"users"=>"read", "articles"=>"write"' + Hstore.reset_column_information - def test_change_table_supports_hstore - @connection.transaction do - @connection.change_table("hstores") do |t| - t.hstore "users", default: "" - end - Hstore.reset_column_information - column = Hstore.columns_hash["users"] - assert_equal :hstore, column.type + assert_equal({ "users" => "read", "articles" => "write" }, Hstore.column_defaults["permissions"]) + assert_equal({ "users" => "read", "articles" => "write" }, Hstore.new.permissions) + ensure + Hstore.reset_column_information + end - raise ActiveRecord::Rollback # reset the schema change + def test_change_table_supports_hstore + @connection.transaction do + @connection.change_table("hstores") do |t| + t.hstore "users", default: "" end - ensure Hstore.reset_column_information + column = Hstore.columns_hash["users"] + assert_equal :hstore, column.type + + raise ActiveRecord::Rollback # reset the schema change end + ensure + Hstore.reset_column_information + end - def test_hstore_migration - hstore_migration = Class.new(ActiveRecord::Migration::Current) do - def change - change_table("hstores") do |t| - t.hstore :keys - end + def test_hstore_migration + hstore_migration = Class.new(ActiveRecord::Migration::Current) do + def change + change_table("hstores") do |t| + t.hstore :keys end end - - hstore_migration.new.suppress_messages do - hstore_migration.migrate(:up) - assert_includes @connection.columns(:hstores).map(&:name), "keys" - hstore_migration.migrate(:down) - assert_not_includes @connection.columns(:hstores).map(&:name), "keys" - end end - def test_cast_value_on_write - x = Hstore.new tags: { "bool" => true, "number" => 5 } - assert_equal({ "bool" => true, "number" => 5 }, x.tags_before_type_cast) - assert_equal({ "bool" => "true", "number" => "5" }, x.tags) - x.save - assert_equal({ "bool" => "true", "number" => "5" }, x.reload.tags) + hstore_migration.new.suppress_messages do + hstore_migration.migrate(:up) + assert_includes @connection.columns(:hstores).map(&:name), "keys" + hstore_migration.migrate(:down) + assert_not_includes @connection.columns(:hstores).map(&:name), "keys" end + end - def test_type_cast_hstore - assert_equal({ "1" => "2" }, @type.deserialize("\"1\"=>\"2\"")) - assert_equal({}, @type.deserialize("")) - assert_equal({ "key" => nil }, @type.deserialize("key => NULL")) - assert_equal({ "c" => "}", '"a"' => 'b "a b' }, @type.deserialize(%q(c=>"}", "\"a\""=>"b \"a b"))) - end + def test_cast_value_on_write + x = Hstore.new tags: { "bool" => true, "number" => 5 } + assert_equal({ "bool" => true, "number" => 5 }, x.tags_before_type_cast) + assert_equal({ "bool" => "true", "number" => "5" }, x.tags) + x.save + assert_equal({ "bool" => "true", "number" => "5" }, x.reload.tags) + end - def test_with_store_accessors - x = Hstore.new(language: "fr", timezone: "GMT") - assert_equal "fr", x.language - assert_equal "GMT", x.timezone + def test_type_cast_hstore + assert_equal({ "1" => "2" }, @type.deserialize("\"1\"=>\"2\"")) + assert_equal({}, @type.deserialize("")) + assert_equal({ "key" => nil }, @type.deserialize("key => NULL")) + assert_equal({ "c" => "}", '"a"' => 'b "a b' }, @type.deserialize(%q(c=>"}", "\"a\""=>"b \"a b"))) + end - x.save! - x = Hstore.first - assert_equal "fr", x.language - assert_equal "GMT", x.timezone + def test_with_store_accessors + x = Hstore.new(language: "fr", timezone: "GMT") + assert_equal "fr", x.language + assert_equal "GMT", x.timezone - x.language = "de" - x.save! + x.save! + x = Hstore.first + assert_equal "fr", x.language + assert_equal "GMT", x.timezone - x = Hstore.first - assert_equal "de", x.language - assert_equal "GMT", x.timezone - end + x.language = "de" + x.save! - def test_duplication_with_store_accessors - x = Hstore.new(language: "fr", timezone: "GMT") - assert_equal "fr", x.language - assert_equal "GMT", x.timezone + x = Hstore.first + assert_equal "de", x.language + assert_equal "GMT", x.timezone + end - y = x.dup - assert_equal "fr", y.language - assert_equal "GMT", y.timezone - end + def test_duplication_with_store_accessors + x = Hstore.new(language: "fr", timezone: "GMT") + assert_equal "fr", x.language + assert_equal "GMT", x.timezone - def test_yaml_round_trip_with_store_accessors - x = Hstore.new(language: "fr", timezone: "GMT") - assert_equal "fr", x.language - assert_equal "GMT", x.timezone + y = x.dup + assert_equal "fr", y.language + assert_equal "GMT", y.timezone + end - y = YAML.load(YAML.dump(x)) - assert_equal "fr", y.language - assert_equal "GMT", y.timezone - end + def test_yaml_round_trip_with_store_accessors + x = Hstore.new(language: "fr", timezone: "GMT") + assert_equal "fr", x.language + assert_equal "GMT", x.timezone - def test_changes_in_place - hstore = Hstore.create!(settings: { "one" => "two" }) - hstore.settings["three"] = "four" - hstore.save! - hstore.reload + y = YAML.load(YAML.dump(x)) + assert_equal "fr", y.language + assert_equal "GMT", y.timezone + end - assert_equal "four", hstore.settings["three"] - assert_not hstore.changed? - end + def test_changes_in_place + hstore = Hstore.create!(settings: { "one" => "two" }) + hstore.settings["three"] = "four" + hstore.save! + hstore.reload - def test_dirty_from_user_equal - settings = { "alongkey" => "anything", "key" => "value" } - hstore = Hstore.create!(settings: settings) + assert_equal "four", hstore.settings["three"] + assert_not_predicate hstore, :changed? + end - hstore.settings = { "key" => "value", "alongkey" => "anything" } - assert_equal settings, hstore.settings - refute hstore.changed? - end + def test_dirty_from_user_equal + settings = { "alongkey" => "anything", "key" => "value" } + hstore = Hstore.create!(settings: settings) - def test_hstore_dirty_from_database_equal - settings = { "alongkey" => "anything", "key" => "value" } - hstore = Hstore.create!(settings: settings) - hstore.reload + hstore.settings = { "key" => "value", "alongkey" => "anything" } + assert_equal settings, hstore.settings + assert_not_predicate hstore, :changed? + end - assert_equal settings, hstore.settings - hstore.settings = settings - refute hstore.changed? - end + def test_hstore_dirty_from_database_equal + settings = { "alongkey" => "anything", "key" => "value" } + hstore = Hstore.create!(settings: settings) + hstore.reload - def test_gen1 - assert_equal('" "=>""', @type.serialize(" " => "")) - end + assert_equal settings, hstore.settings + hstore.settings = settings + assert_not_predicate hstore, :changed? + end - def test_gen2 - assert_equal('","=>""', @type.serialize("," => "")) - end + def test_gen1 + assert_equal('" "=>""', @type.serialize(" " => "")) + end - def test_gen3 - assert_equal('"="=>""', @type.serialize("=" => "")) - end + def test_gen2 + assert_equal('","=>""', @type.serialize("," => "")) + end - def test_gen4 - assert_equal('">"=>""', @type.serialize(">" => "")) - end + def test_gen3 + assert_equal('"="=>""', @type.serialize("=" => "")) + end - def test_parse1 - assert_equal({ "a" => nil, "b" => nil, "c" => "NuLl", "null" => "c" }, @type.deserialize('a=>null,b=>NuLl,c=>"NuLl",null=>c')) - end + def test_gen4 + assert_equal('">"=>""', @type.serialize(">" => "")) + end - def test_parse2 - assert_equal({ " " => " " }, @type.deserialize("\\ =>\\ ")) - end + def test_parse1 + assert_equal({ "a" => nil, "b" => nil, "c" => "NuLl", "null" => "c" }, @type.deserialize('a=>null,b=>NuLl,c=>"NuLl",null=>c')) + end - def test_parse3 - assert_equal({ "=" => ">" }, @type.deserialize("==>>")) - end + def test_parse2 + assert_equal({ " " => " " }, @type.deserialize("\\ =>\\ ")) + end - def test_parse4 - assert_equal({ "=a" => "q=w" }, @type.deserialize('\=a=>q=w')) - end + def test_parse3 + assert_equal({ "=" => ">" }, @type.deserialize("==>>")) + end - def test_parse5 - assert_equal({ "=a" => "q=w" }, @type.deserialize('"=a"=>q\=w')) - end + def test_parse4 + assert_equal({ "=a" => "q=w" }, @type.deserialize('\=a=>q=w')) + end - def test_parse6 - assert_equal({ "\"a" => "q>w" }, @type.deserialize('"\"a"=>q>w')) - end + def test_parse5 + assert_equal({ "=a" => "q=w" }, @type.deserialize('"=a"=>q\=w')) + end - def test_parse7 - assert_equal({ "\"a" => "q\"w" }, @type.deserialize('\"a=>q"w')) - end + def test_parse6 + assert_equal({ "\"a" => "q>w" }, @type.deserialize('"\"a"=>q>w')) + end - def test_rewrite - @connection.execute "insert into hstores (tags) VALUES ('1=>2')" - x = Hstore.first - x.tags = { '"a\'' => "b" } - assert x.save! - end + def test_parse7 + assert_equal({ "\"a" => "q\"w" }, @type.deserialize('\"a=>q"w')) + end - def test_select - @connection.execute "insert into hstores (tags) VALUES ('1=>2')" - x = Hstore.first - assert_equal({ "1" => "2" }, x.tags) - end + def test_rewrite + @connection.execute "insert into hstores (tags) VALUES ('1=>2')" + x = Hstore.first + x.tags = { '"a\'' => "b" } + assert x.save! + end - def test_array_cycle - assert_array_cycle([{ "AA" => "BB", "CC" => "DD" }, { "AA" => nil }]) - end + def test_select + @connection.execute "insert into hstores (tags) VALUES ('1=>2')" + x = Hstore.first + assert_equal({ "1" => "2" }, x.tags) + end - def test_array_strings_with_quotes - assert_array_cycle([{ "this has" => 'some "s that need to be escaped"' }]) - end + def test_array_cycle + assert_array_cycle([{ "AA" => "BB", "CC" => "DD" }, { "AA" => nil }]) + end - def test_array_strings_with_commas - assert_array_cycle([{ "this,has" => "many,values" }]) - end + def test_array_strings_with_quotes + assert_array_cycle([{ "this has" => 'some "s that need to be escaped"' }]) + end - def test_array_strings_with_array_delimiters - assert_array_cycle(["{" => "}"]) - end + def test_array_strings_with_commas + assert_array_cycle([{ "this,has" => "many,values" }]) + end - def test_array_strings_with_null_strings - assert_array_cycle([{ "NULL" => "NULL" }]) - end + def test_array_strings_with_array_delimiters + assert_array_cycle(["{" => "}"]) + end - def test_contains_nils - assert_array_cycle([{ "NULL" => nil }]) - end + def test_array_strings_with_null_strings + assert_array_cycle([{ "NULL" => "NULL" }]) + end - def test_select_multikey - @connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')" - x = Hstore.first - assert_equal({ "1" => "2", "2" => "3" }, x.tags) - end + def test_contains_nils + assert_array_cycle([{ "NULL" => nil }]) + end - def test_create - assert_cycle("a" => "b", "1" => "2") - end + def test_select_multikey + @connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')" + x = Hstore.first + assert_equal({ "1" => "2", "2" => "3" }, x.tags) + end - def test_nil - assert_cycle("a" => nil) - end + def test_create + assert_cycle("a" => "b", "1" => "2") + end - def test_quotes - assert_cycle("a" => 'b"ar', '1"foo' => "2") - end + def test_nil + assert_cycle("a" => nil) + end - def test_whitespace - assert_cycle("a b" => "b ar", '1"foo' => "2") - end + def test_quotes + assert_cycle("a" => 'b"ar', '1"foo' => "2") + end - def test_backslash - assert_cycle('a\\b' => 'b\\ar', '1"foo' => "2") - end + def test_whitespace + assert_cycle("a b" => "b ar", '1"foo' => "2") + end - def test_comma - assert_cycle("a, b" => "bar", '1"foo' => "2") - end + def test_backslash + assert_cycle('a\\b' => 'b\\ar', '1"foo' => "2") + end - def test_arrow - assert_cycle("a=>b" => "bar", '1"foo' => "2") - end + def test_comma + assert_cycle("a, b" => "bar", '1"foo' => "2") + end - def test_quoting_special_characters - assert_cycle("ca" => "cà", "ac" => "àc") - end + def test_arrow + assert_cycle("a=>b" => "bar", '1"foo' => "2") + end - def test_multiline - assert_cycle("a\nb" => "c\nd") - end + def test_quoting_special_characters + assert_cycle("ca" => "cà", "ac" => "àc") + end - class TagCollection - def initialize(hash); @hash = hash end - def to_hash; @hash end - def self.load(hash); new(hash) end - def self.dump(object); object.to_hash end - end + def test_multiline + assert_cycle("a\nb" => "c\nd") + end - class HstoreWithSerialize < Hstore - serialize :tags, TagCollection - end + class TagCollection + def initialize(hash); @hash = hash end + def to_hash; @hash end + def self.load(hash); new(hash) end + def self.dump(object); object.to_hash end + end - def test_hstore_with_serialized_attributes - HstoreWithSerialize.create! tags: TagCollection.new("one" => "two") - record = HstoreWithSerialize.first - assert_instance_of TagCollection, record.tags - assert_equal({ "one" => "two" }, record.tags.to_hash) - record.tags = TagCollection.new("three" => "four") - record.save! - assert_equal({ "three" => "four" }, HstoreWithSerialize.first.tags.to_hash) - end + class HstoreWithSerialize < Hstore + serialize :tags, TagCollection + end - def test_clone_hstore_with_serialized_attributes - HstoreWithSerialize.create! tags: TagCollection.new("one" => "two") - record = HstoreWithSerialize.first - dupe = record.dup - assert_equal({ "one" => "two" }, dupe.tags.to_hash) - end + def test_hstore_with_serialized_attributes + HstoreWithSerialize.create! tags: TagCollection.new("one" => "two") + record = HstoreWithSerialize.first + assert_instance_of TagCollection, record.tags + assert_equal({ "one" => "two" }, record.tags.to_hash) + record.tags = TagCollection.new("three" => "four") + record.save! + assert_equal({ "three" => "four" }, HstoreWithSerialize.first.tags.to_hash) + end - def test_schema_dump_with_shorthand - output = dump_table_schema("hstores") - assert_match %r[t\.hstore "tags",\s+default: {}], output - end + def test_clone_hstore_with_serialized_attributes + HstoreWithSerialize.create! tags: TagCollection.new("one" => "two") + record = HstoreWithSerialize.first + dupe = record.dup + assert_equal({ "one" => "two" }, dupe.tags.to_hash) + end + + def test_schema_dump_with_shorthand + output = dump_table_schema("hstores") + assert_match %r[t\.hstore "tags",\s+default: {}], output + end + + def test_supports_to_unsafe_h_values + assert_equal("\"hi\"=>\"hi\"", @type.serialize(FakeParameters.new)) + end - def test_supports_to_unsafe_h_values - assert_equal("\"hi\"=>\"hi\"", @type.serialize(FakeParameters.new)) + private + def assert_array_cycle(array) + # test creation + x = Hstore.create!(payload: array) + x.reload + assert_equal(array, x.payload) + + # test updating + x = Hstore.create!(payload: []) + x.payload = array + x.save! + x.reload + assert_equal(array, x.payload) end - private - def assert_array_cycle(array) - # test creation - x = Hstore.create!(payload: array) - x.reload - assert_equal(array, x.payload) - - # test updating - x = Hstore.create!(payload: []) - x.payload = array - x.save! - x.reload - assert_equal(array, x.payload) - end + def assert_cycle(hash) + # test creation + x = Hstore.create!(tags: hash) + x.reload + assert_equal(hash, x.tags) - def assert_cycle(hash) - # test creation - x = Hstore.create!(tags: hash) - x.reload - assert_equal(hash, x.tags) - - # test updating - x = Hstore.create!(tags: {}) - x.tags = hash - x.save! - x.reload - assert_equal(hash, x.tags) - end - end + # test updating + x = Hstore.create!(tags: {}) + x.tags = hash + x.save! + x.reload + assert_equal(hash, x.tags) + end end diff --git a/activerecord/test/cases/adapters/postgresql/infinity_test.rb b/activerecord/test/cases/adapters/postgresql/infinity_test.rb index b9e177e6ec..5e56ce8427 100644 --- a/activerecord/test/cases/adapters/postgresql/infinity_test.rb +++ b/activerecord/test/cases/adapters/postgresql/infinity_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" class PostgresqlInfinityTest < ActiveRecord::PostgreSQLTestCase @@ -11,6 +13,7 @@ class PostgresqlInfinityTest < ActiveRecord::PostgreSQLTestCase @connection.create_table(:postgresql_infinities) do |t| t.float :float t.datetime :datetime + t.date :date end end @@ -41,11 +44,25 @@ class PostgresqlInfinityTest < ActiveRecord::PostgreSQLTestCase end test "type casting infinity on a datetime column" do + record = PostgresqlInfinity.create!(datetime: "infinity") + record.reload + assert_equal Float::INFINITY, record.datetime + record = PostgresqlInfinity.create!(datetime: Float::INFINITY) record.reload assert_equal Float::INFINITY, record.datetime end + test "type casting infinity on a date column" do + record = PostgresqlInfinity.create!(date: "infinity") + record.reload + assert_equal Float::INFINITY, record.date + + record = PostgresqlInfinity.create!(date: Float::INFINITY) + record.reload + assert_equal Float::INFINITY, record.date + end + test "update_all with infinity on a datetime column" do record = PostgresqlInfinity.create! PostgresqlInfinity.update_all(datetime: Float::INFINITY) @@ -66,4 +83,28 @@ class PostgresqlInfinityTest < ActiveRecord::PostgreSQLTestCase PostgresqlInfinity.reset_column_information end end + + test "where clause with infinite range on a datetime column" do + record = PostgresqlInfinity.create!(datetime: Time.current) + + string = PostgresqlInfinity.where(datetime: "-infinity".."infinity") + assert_equal record, string.take + + infinity = PostgresqlInfinity.where(datetime: -::Float::INFINITY..::Float::INFINITY) + assert_equal record, infinity.take + + assert_equal infinity.to_sql, string.to_sql + end + + test "where clause with infinite range on a date column" do + record = PostgresqlInfinity.create!(date: Date.current) + + string = PostgresqlInfinity.where(date: "-infinity".."infinity") + assert_equal record, string.take + + infinity = PostgresqlInfinity.where(date: -::Float::INFINITY..::Float::INFINITY) + assert_equal record, infinity.take + + assert_equal infinity.to_sql, string.to_sql + end end diff --git a/activerecord/test/cases/adapters/postgresql/integer_test.rb b/activerecord/test/cases/adapters/postgresql/integer_test.rb index b4e55964b9..3e45b057ff 100644 --- a/activerecord/test/cases/adapters/postgresql/integer_test.rb +++ b/activerecord/test/cases/adapters/postgresql/integer_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "active_support/core_ext/numeric/bytes" diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index 93558ac4d2..ee08841eb3 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -1,212 +1,37 @@ +# frozen_string_literal: true + require "cases/helper" -require "support/schema_dumping_helper" +require "cases/json_shared_test_cases" module PostgresqlJSONSharedTestCases - include SchemaDumpingHelper - - class JsonDataType < ActiveRecord::Base - self.table_name = "json_data_type" - - store_accessor :settings, :resolution - end + include JSONSharedTestCases def setup - @connection = ActiveRecord::Base.connection - begin - @connection.create_table("json_data_type") do |t| - t.public_send column_type, "payload", default: {} # t.json 'payload', default: {} - t.public_send column_type, "settings" # t.json 'settings' - end - rescue ActiveRecord::StatementInvalid - skip "do not test on PostgreSQL without #{column_type} type." + super + @connection.create_table("json_data_type") do |t| + t.public_send column_type, "payload", default: {} # t.json 'payload', default: {} + t.public_send column_type, "settings" # t.json 'settings' + t.public_send column_type, "objects", array: true # t.json 'objects', array: true end - end - - def teardown - @connection.drop_table :json_data_type, if_exists: true - JsonDataType.reset_column_information - end - - def test_column - column = JsonDataType.columns_hash["payload"] - assert_equal column_type, column.type - assert_equal column_type.to_s, column.sql_type - assert_not column.array? - - type = JsonDataType.type_for_attribute("payload") - assert_not type.binary? + rescue ActiveRecord::StatementInvalid + skip "do not test on PostgreSQL without #{column_type} type." end def test_default @connection.add_column "json_data_type", "permissions", column_type, default: { "users": "read", "posts": ["read", "write"] } - JsonDataType.reset_column_information - - assert_equal({ "users" => "read", "posts" => ["read", "write"] }, JsonDataType.column_defaults["permissions"]) - assert_equal({ "users" => "read", "posts" => ["read", "write"] }, JsonDataType.new.permissions) - ensure - JsonDataType.reset_column_information - end - - def test_change_table_supports_json - @connection.transaction do - @connection.change_table("json_data_type") do |t| - t.public_send column_type, "users", default: "{}" # t.json 'users', default: '{}' - end - JsonDataType.reset_column_information - column = JsonDataType.columns_hash["users"] - assert_equal column_type, column.type - - raise ActiveRecord::Rollback # reset the schema change - end - ensure - JsonDataType.reset_column_information - end - - def test_schema_dumping - output = dump_table_schema("json_data_type") - assert_match(/t\.#{column_type.to_s}\s+"payload",\s+default: {}/, output) - end - - def test_cast_value_on_write - x = JsonDataType.new payload: { "string" => "foo", :symbol => :bar } - assert_equal({ "string" => "foo", :symbol => :bar }, x.payload_before_type_cast) - assert_equal({ "string" => "foo", "symbol" => "bar" }, x.payload) - x.save - assert_equal({ "string" => "foo", "symbol" => "bar" }, x.reload.payload) - end - - def test_type_cast_json - type = JsonDataType.type_for_attribute("payload") - - data = "{\"a_key\":\"a_value\"}" - hash = type.deserialize(data) - assert_equal({ "a_key" => "a_value" }, hash) - assert_equal({ "a_key" => "a_value" }, type.deserialize(data)) - - assert_equal({}, type.deserialize("{}")) - assert_equal({ "key" => nil }, type.deserialize('{"key": null}')) - assert_equal({ "c" => "}", '"a"' => 'b "a b' }, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"}))) - end - - def test_rewrite - @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')" - x = JsonDataType.first - x.payload = { '"a\'' => "b" } - assert x.save! - end - - def test_select - @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')" - x = JsonDataType.first - assert_equal({ "k" => "v" }, x.payload) - end - - def test_select_multikey - @connection.execute %q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')| - x = JsonDataType.first - assert_equal({ "k1" => "v1", "k2" => "v2", "k3" => [1, 2, 3] }, x.payload) - end - - def test_null_json - @connection.execute "insert into json_data_type (payload) VALUES(null)" - x = JsonDataType.first - assert_nil(x.payload) - end + klass.reset_column_information - def test_select_nil_json_after_create - json = JsonDataType.create(payload: nil) - x = JsonDataType.where(payload: nil).first - assert_equal(json, x) + assert_equal({ "users" => "read", "posts" => ["read", "write"] }, klass.column_defaults["permissions"]) + assert_equal({ "users" => "read", "posts" => ["read", "write"] }, klass.new.permissions) end - def test_select_nil_json_after_update - json = JsonDataType.create(payload: "foo") - x = JsonDataType.where(payload: nil).first - assert_nil(x) - - json.update_attributes payload: nil - x = JsonDataType.where(payload: nil).first - assert_equal(json.reload, x) - end - - def test_select_array_json_value - @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| - x = JsonDataType.first - assert_equal(["v0", { "k1" => "v1" }], x.payload) - end - - def test_rewrite_array_json_value - @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| - x = JsonDataType.first - x.payload = ["v1", { "k2" => "v2" }, "v3"] - assert x.save! - end - - def test_with_store_accessors - x = JsonDataType.new(resolution: "320×480") - assert_equal "320×480", x.resolution - + def test_deserialize_with_array + x = klass.new(objects: ["foo" => "bar"]) + assert_equal ["foo" => "bar"], x.objects x.save! - x = JsonDataType.first - assert_equal "320×480", x.resolution - - x.resolution = "640×1136" - x.save! - - x = JsonDataType.first - assert_equal "640×1136", x.resolution - end - - def test_duplication_with_store_accessors - x = JsonDataType.new(resolution: "320×480") - assert_equal "320×480", x.resolution - - y = x.dup - assert_equal "320×480", y.resolution - end - - def test_yaml_round_trip_with_store_accessors - x = JsonDataType.new(resolution: "320×480") - assert_equal "320×480", x.resolution - - y = YAML.load(YAML.dump(x)) - assert_equal "320×480", y.resolution - end - - def test_changes_in_place - json = JsonDataType.new - assert_not json.changed? - - json.payload = { "one" => "two" } - assert json.changed? - assert json.payload_changed? - - json.save! - assert_not json.changed? - - json.payload["three"] = "four" - assert json.payload_changed? - - json.save! - json.reload - - assert_equal({ "one" => "two", "three" => "four" }, json.payload) - assert_not json.changed? - end - - def test_assigning_string_literal - json = JsonDataType.create(payload: "foo") - assert_equal "foo", json.payload - end - - def test_assigning_number - json = JsonDataType.create(payload: 1.234) - assert_equal 1.234, json.payload - end - - def test_assigning_boolean - json = JsonDataType.create(payload: true) - assert_equal true, json.payload + assert_equal ["foo" => "bar"], x.objects + x.reload + assert_equal ["foo" => "bar"], x.objects end end diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb index 2b5ac1cac6..8349ee6ee2 100644 --- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb +++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/schema_dumping_helper" @@ -29,10 +31,10 @@ class PostgresqlLtreeTest < ActiveRecord::PostgreSQLTestCase column = Ltree.columns_hash["path"] assert_equal :ltree, column.type assert_equal "ltree", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = Ltree.type_for_attribute("path") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_write diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb index 1b5d8362af..61e75e772d 100644 --- a/activerecord/test/cases/adapters/postgresql/money_test.rb +++ b/activerecord/test/cases/adapters/postgresql/money_test.rb @@ -1,10 +1,14 @@ +# frozen_string_literal: true + require "cases/helper" require "support/schema_dumping_helper" class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper - class PostgresqlMoney < ActiveRecord::Base; end + class PostgresqlMoney < ActiveRecord::Base + validates :depth, numericality: true + end setup do @connection = ActiveRecord::Base.connection @@ -24,15 +28,16 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase assert_equal :money, column.type assert_equal "money", column.sql_type assert_equal 2, column.scale - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlMoney.type_for_attribute("wealth") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_default - assert_equal BigDecimal.new("150.55"), PostgresqlMoney.column_defaults["depth"] - assert_equal BigDecimal.new("150.55"), PostgresqlMoney.new.depth + assert_equal BigDecimal("150.55"), PostgresqlMoney.column_defaults["depth"] + assert_equal BigDecimal("150.55"), PostgresqlMoney.new.depth + assert_equal "$150.55", PostgresqlMoney.new.depth_before_type_cast end def test_money_values @@ -47,10 +52,10 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase def test_money_type_cast 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(-1.15, type.cast("-$1.15")) - assert_equal(-2.25, type.cast("($2.25)")) + assert_equal(12345678.12, type.cast("$12,345,678.12".dup)) + assert_equal(12345678.12, type.cast("$12.345.678,12".dup)) + assert_equal(-1.15, type.cast("-$1.15".dup)) + assert_equal(-2.25, type.cast("($2.25)".dup)) end def test_schema_dumping @@ -60,10 +65,10 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase end def test_create_and_update_money - money = PostgresqlMoney.create(wealth: "987.65") + money = PostgresqlMoney.create(wealth: "987.65".dup) assert_equal 987.65, money.wealth - new_value = BigDecimal.new("123.45") + new_value = BigDecimal("123.45") money.wealth = new_value money.save! money.reload diff --git a/activerecord/test/cases/adapters/postgresql/network_test.rb b/activerecord/test/cases/adapters/postgresql/network_test.rb index a33b0ef8a7..736570451b 100644 --- a/activerecord/test/cases/adapters/postgresql/network_test.rb +++ b/activerecord/test/cases/adapters/postgresql/network_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/schema_dumping_helper" @@ -22,30 +24,30 @@ class PostgresqlNetworkTest < ActiveRecord::PostgreSQLTestCase column = PostgresqlNetworkAddress.columns_hash["cidr_address"] assert_equal :cidr, column.type assert_equal "cidr", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlNetworkAddress.type_for_attribute("cidr_address") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_inet_column column = PostgresqlNetworkAddress.columns_hash["inet_address"] assert_equal :inet, column.type assert_equal "inet", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlNetworkAddress.type_for_attribute("inet_address") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_macaddr_column column = PostgresqlNetworkAddress.columns_hash["mac_address"] assert_equal :macaddr, column.type assert_equal "macaddr", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlNetworkAddress.type_for_attribute("mac_address") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_network_types diff --git a/activerecord/test/cases/adapters/postgresql/numbers_test.rb b/activerecord/test/cases/adapters/postgresql/numbers_test.rb index bfb2b7c27a..b53a12254d 100644 --- a/activerecord/test/cases/adapters/postgresql/numbers_test.rb +++ b/activerecord/test/cases/adapters/postgresql/numbers_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" class PostgresqlNumberTest < ActiveRecord::PostgreSQLTestCase diff --git a/activerecord/test/cases/adapters/postgresql/partitions_test.rb b/activerecord/test/cases/adapters/postgresql/partitions_test.rb new file mode 100644 index 0000000000..0ac9ca1200 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/partitions_test.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "cases/helper" + +class PostgreSQLPartitionsTest < ActiveRecord::PostgreSQLTestCase + def setup + @connection = ActiveRecord::Base.connection + end + + def teardown + @connection.drop_table "partitioned_events", if_exists: true + end + + def test_partitions_table_exists + skip unless ActiveRecord::Base.connection.postgresql_version >= 100000 + @connection.create_table :partitioned_events, force: true, id: false, + options: "partition by range (issued_at)" do |t| + t.timestamp :issued_at + end + assert @connection.table_exists?("partitioned_events") + end +end diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 3054f0271f..cbb6cd42b5 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/ddl_helper" require "support/connection_helper" @@ -21,17 +23,6 @@ module ActiveRecord end end - def test_valid_column - with_example_table do - column = @connection.columns("ex").find { |col| col.name == "id" } - assert @connection.valid_type?(column.type) - end - end - - def test_invalid_column - assert_not @connection.valid_type?(:foobar) - end - def test_primary_key with_example_table do assert_equal "id", @connection.primary_key("ex") @@ -257,12 +248,12 @@ module ActiveRecord def test_index_with_opclass with_example_table do - @connection.add_index "ex", "data varchar_pattern_ops" - index = @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data_varchar_pattern_ops" } - assert_equal "data varchar_pattern_ops", index.columns + @connection.add_index "ex", "data", opclass: "varchar_pattern_ops" + index = @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data" } + assert_equal ["data"], index.columns - @connection.remove_index "ex", "data varchar_pattern_ops" - assert_not @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data_varchar_pattern_ops" } + @connection.remove_index "ex", "data" + assert_not @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data" } end end @@ -272,25 +263,25 @@ module ActiveRecord end def test_columns_for_distinct_one_order - assert_equal "posts.id, posts.created_at AS alias_0", + assert_equal "posts.created_at AS alias_0, posts.id", @connection.columns_for_distinct("posts.id", ["posts.created_at desc"]) end def test_columns_for_distinct_few_orders - assert_equal "posts.id, posts.created_at AS alias_0, posts.position AS alias_1", + assert_equal "posts.created_at AS alias_0, posts.position AS alias_1, posts.id", @connection.columns_for_distinct("posts.id", ["posts.created_at desc", "posts.position asc"]) end def test_columns_for_distinct_with_case assert_equal( - "posts.id, CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END AS alias_0", + "CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END AS alias_0, posts.id", @connection.columns_for_distinct("posts.id", ["CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END"]) ) end def test_columns_for_distinct_blank_not_nil_orders - assert_equal "posts.id, posts.created_at AS alias_0", + assert_equal "posts.created_at AS alias_0, posts.id", @connection.columns_for_distinct("posts.id", ["posts.created_at desc", "", " "]) end @@ -299,23 +290,23 @@ module ActiveRecord def order.to_sql "posts.created_at desc" end - assert_equal "posts.id, posts.created_at AS alias_0", + assert_equal "posts.created_at AS alias_0, posts.id", @connection.columns_for_distinct("posts.id", [order]) end def test_columns_for_distinct_with_nulls - assert_equal "posts.title, posts.updater_id AS alias_0", @connection.columns_for_distinct("posts.title", ["posts.updater_id desc nulls first"]) - assert_equal "posts.title, posts.updater_id AS alias_0", @connection.columns_for_distinct("posts.title", ["posts.updater_id desc nulls last"]) + assert_equal "posts.updater_id AS alias_0, posts.title", @connection.columns_for_distinct("posts.title", ["posts.updater_id desc nulls first"]) + assert_equal "posts.updater_id AS alias_0, posts.title", @connection.columns_for_distinct("posts.title", ["posts.updater_id desc nulls last"]) end def test_columns_for_distinct_without_order_specifiers - assert_equal "posts.title, posts.updater_id AS alias_0", + assert_equal "posts.updater_id AS alias_0, posts.title", @connection.columns_for_distinct("posts.title", ["posts.updater_id"]) - assert_equal "posts.title, posts.updater_id AS alias_0", + assert_equal "posts.updater_id AS alias_0, posts.title", @connection.columns_for_distinct("posts.title", ["posts.updater_id nulls last"]) - assert_equal "posts.title, posts.updater_id AS alias_0", + assert_equal "posts.updater_id AS alias_0, posts.title", @connection.columns_for_distinct("posts.title", ["posts.updater_id nulls first"]) end @@ -335,29 +326,35 @@ module ActiveRecord reset_connection end - def test_only_reload_type_map_once_for_every_unknown_type + def test_only_reload_type_map_once_for_every_unrecognized_type + reset_connection + connection = ActiveRecord::Base.connection + silence_warnings do assert_queries 2, ignore_none: true do - @connection.select_all "SELECT NULL::anyelement" + connection.select_all "select 'pg_catalog.pg_class'::regclass" end assert_queries 1, ignore_none: true do - @connection.select_all "SELECT NULL::anyelement" + connection.select_all "select 'pg_catalog.pg_class'::regclass" end assert_queries 2, ignore_none: true do - @connection.select_all "SELECT NULL::anyarray" + connection.select_all "SELECT NULL::anyarray" end end ensure reset_connection end - def test_only_warn_on_first_encounter_of_unknown_oid + def test_only_warn_on_first_encounter_of_unrecognized_oid + reset_connection + connection = ActiveRecord::Base.connection + warning = capture(:stderr) { - @connection.select_all "SELECT NULL::anyelement" - @connection.select_all "SELECT NULL::anyelement" - @connection.select_all "SELECT NULL::anyelement" + connection.select_all "select 'pg_catalog.pg_class'::regclass" + connection.select_all "select 'pg_catalog.pg_class'::regclass" + connection.select_all "select 'pg_catalog.pg_class'::regclass" } - assert_match(/\Aunknown OID \d+: failed to recognize type of 'anyelement'. It will be treated as String.\n\z/, warning) + assert_match(/\Aunknown OID \d+: failed to recognize type of 'regclass'\. It will be treated as String\.\n\z/, warning) ensure reset_connection end diff --git a/activerecord/test/cases/adapters/postgresql/prepared_statements_disabled_test.rb b/activerecord/test/cases/adapters/postgresql/prepared_statements_disabled_test.rb index 8c62690866..f7478b50c3 100644 --- a/activerecord/test/cases/adapters/postgresql/prepared_statements_disabled_test.rb +++ b/activerecord/test/cases/adapters/postgresql/prepared_statements_disabled_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/computer" require "models/developer" diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb index a1e966b915..d50dc49276 100644 --- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb +++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" module ActiveRecord @@ -9,11 +11,11 @@ module ActiveRecord end def test_type_cast_true - assert_equal "t", @conn.type_cast(true) + assert_equal true, @conn.type_cast(true) end def test_type_cast_false - assert_equal "f", @conn.type_cast(false) + assert_equal false, @conn.type_cast(false) end def test_quote_float_nan diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb index f411884dfd..433598500d 100644 --- a/activerecord/test/cases/adapters/postgresql/range_test.rb +++ b/activerecord/test/cases/adapters/postgresql/range_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/connection_helper" @@ -132,10 +134,10 @@ _SQL end def test_numrange_values - assert_equal BigDecimal.new("0.1")..BigDecimal.new("0.2"), @first_range.num_range - assert_equal BigDecimal.new("0.1")...BigDecimal.new("0.2"), @second_range.num_range - assert_equal BigDecimal.new("0.1")...BigDecimal.new("Infinity"), @third_range.num_range - assert_equal BigDecimal.new("-Infinity")...BigDecimal.new("Infinity"), @fourth_range.num_range + assert_equal BigDecimal("0.1")..BigDecimal("0.2"), @first_range.num_range + assert_equal BigDecimal("0.1")...BigDecimal("0.2"), @second_range.num_range + assert_equal BigDecimal("0.1")...BigDecimal("Infinity"), @third_range.num_range + assert_equal BigDecimal("-Infinity")...BigDecimal("Infinity"), @fourth_range.num_range assert_nil @empty_range.num_range end @@ -230,16 +232,67 @@ _SQL end end + def test_create_tstzrange_preserve_usec + tstzrange = Time.parse("2010-01-01 14:30:00.670277 +0100")...Time.parse("2011-02-02 14:30:00.745125 CDT") + round_trip(@new_range, :tstz_range, tstzrange) + assert_equal @new_range.tstz_range, tstzrange + assert_equal @new_range.tstz_range, Time.parse("2010-01-01 13:30:00.670277 UTC")...Time.parse("2011-02-02 19:30:00.745125 UTC") + end + + def test_update_tstzrange_preserve_usec + assert_equal_round_trip(@first_range, :tstz_range, + Time.parse("2010-01-01 14:30:00.245124 CDT")...Time.parse("2011-02-02 14:30:00.451274 CET")) + assert_nil_round_trip(@first_range, :tstz_range, + Time.parse("2010-01-01 14:30:00.245124 +0100")...Time.parse("2010-01-01 13:30:00.245124 +0000")) + end + + def test_create_tsrange_preseve_usec + tz = ::ActiveRecord::Base.default_timezone + assert_equal_round_trip(@new_range, :ts_range, + Time.send(tz, 2010, 1, 1, 14, 30, 0, 125435)...Time.send(tz, 2011, 2, 2, 14, 30, 0, 225435)) + end + + def test_update_tsrange_preserve_usec + tz = ::ActiveRecord::Base.default_timezone + assert_equal_round_trip(@first_range, :ts_range, + Time.send(tz, 2010, 1, 1, 14, 30, 0, 142432)...Time.send(tz, 2011, 2, 2, 14, 30, 0, 224242)) + assert_nil_round_trip(@first_range, :ts_range, + Time.send(tz, 2010, 1, 1, 14, 30, 0, 142432)...Time.send(tz, 2010, 1, 1, 14, 30, 0, 142432)) + end + + def test_timezone_awareness_tsrange_preserve_usec + tz = "Pacific Time (US & Canada)" + + in_time_zone tz do + PostgresqlRange.reset_column_information + time_string = "2017-09-26 07:30:59.132451 -0700" + time = Time.zone.parse(time_string) + assert time.usec > 0 + + record = PostgresqlRange.new(ts_range: time_string..time_string) + assert_equal time..time, record.ts_range + assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone + assert_equal time.usec, record.ts_range.begin.usec + + record.save! + record.reload + + assert_equal time..time, record.ts_range + assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone + assert_equal time.usec, record.ts_range.begin.usec + end + end + def test_create_numrange assert_equal_round_trip(@new_range, :num_range, - BigDecimal.new("0.5")...BigDecimal.new("1")) + BigDecimal("0.5")...BigDecimal("1")) end def test_update_numrange assert_equal_round_trip(@first_range, :num_range, - BigDecimal.new("0.5")...BigDecimal.new("1")) + BigDecimal("0.5")...BigDecimal("1")) assert_nil_round_trip(@first_range, :num_range, - BigDecimal.new("0.5")...BigDecimal.new("0.5")) + BigDecimal("0.5")...BigDecimal("0.5")) end def test_create_daterange @@ -288,6 +341,12 @@ _SQL assert_equal record, PostgresqlRange.where(int4_range: range).take end + def test_where_by_attribute_with_range_in_array + range = 1..100 + record = PostgresqlRange.create!(int4_range: range) + assert_equal record, PostgresqlRange.where(int4_range: [range]).take + end + def test_update_all_with_ranges PostgresqlRange.create! @@ -305,6 +364,18 @@ _SQL end end + def test_infinity_values + PostgresqlRange.create!(int4_range: 1..Float::INFINITY, + int8_range: -Float::INFINITY..0, + float_range: -Float::INFINITY..Float::INFINITY) + + record = PostgresqlRange.first + + assert_equal(1...Float::INFINITY, record.int4_range) + assert_equal(-Float::INFINITY...1, record.int8_range) + assert_equal(-Float::INFINITY...Float::INFINITY, record.float_range) + end + private def assert_equal_round_trip(range, attribute, value) round_trip(range, attribute, value) diff --git a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb index 0ff04bfa27..0bcc214c24 100644 --- a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb +++ b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/connection_helper" diff --git a/activerecord/test/cases/adapters/postgresql/rename_table_test.rb b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb index e9e7f717ac..100d247113 100644 --- a/activerecord/test/cases/adapters/postgresql/rename_table_test.rb +++ b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" class PostgresqlRenameTableTest < ActiveRecord::PostgreSQLTestCase diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb index f6a07da85f..fcb0aec81b 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" class SchemaThing < ActiveRecord::Base @@ -75,17 +77,6 @@ class SchemaAuthorizationTest < ActiveRecord::PostgreSQLTestCase end end - def test_schema_uniqueness - assert_nothing_raised do - set_session_auth - USERS.each do |u| - set_session_auth u - assert_equal u, @connection.select_value("SELECT name FROM #{TABLE_NAME} WHERE id = 1") - set_session_auth - end - end - end - def test_sequence_schema_caching assert_nothing_raised do USERS.each do |u| diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index 7b065ff320..a36d066c80 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/default" require "support/schema_dumping_helper" @@ -91,6 +93,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase @connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING gin (#{INDEX_E_COLUMN});" @connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_E_COLUMN});" @connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{PK_TABLE_NAME} (id serial primary key)" + @connection.execute "CREATE TABLE #{SCHEMA2_NAME}.#{PK_TABLE_NAME} (id serial primary key)" @connection.execute "CREATE SEQUENCE #{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}" @connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{UNMATCHED_PK_TABLE_NAME} (id integer NOT NULL DEFAULT nextval('#{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}'::regclass), CONSTRAINT unmatched_pkey PRIMARY KEY (id))" end @@ -201,12 +204,12 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase def test_data_source_exists_when_not_on_schema_search_path with_schema_search_path("PUBLIC") do - assert(!@connection.data_source_exists?(TABLE_NAME), "data_source exists but should not be found") + assert_not(@connection.data_source_exists?(TABLE_NAME), "data_source exists but should not be found") end end def test_data_source_exists_wrong_schema - assert(!@connection.data_source_exists?("foo.things"), "data_source should not exist") + assert_not(@connection.data_source_exists?("foo.things"), "data_source should not exist") end def test_data_source_exists_quoted_names @@ -361,7 +364,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase end def test_primary_key_assuming_schema_search_path - with_schema_search_path(SCHEMA_NAME) do + with_schema_search_path("#{SCHEMA_NAME}, #{SCHEMA2_NAME}") do assert_equal "id", @connection.primary_key(PK_TABLE_NAME), "primary key should be found" end end @@ -456,7 +459,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase assert_equal :btree, index_d.using assert_equal :gin, index_e.using - assert_equal :desc, index_d.orders[INDEX_D_COLUMN] + assert_equal :desc, index_d.orders end end @@ -497,6 +500,66 @@ class SchemaForeignKeyTest < ActiveRecord::PostgreSQLTestCase end end +class SchemaIndexOpclassTest < ActiveRecord::PostgreSQLTestCase + include SchemaDumpingHelper + + setup do + @connection = ActiveRecord::Base.connection + @connection.create_table "trains" do |t| + t.string :name + t.text :description + end + end + + teardown do + @connection.drop_table "trains", if_exists: true + end + + def test_string_opclass_is_dumped + @connection.execute "CREATE INDEX trains_name_and_description ON trains USING btree(name text_pattern_ops, description text_pattern_ops)" + + output = dump_table_schema "trains" + + assert_match(/opclass: :text_pattern_ops/, output) + end + + def test_non_default_opclass_is_dumped + @connection.execute "CREATE INDEX trains_name_and_description ON trains USING btree(name, description text_pattern_ops)" + + output = dump_table_schema "trains" + + assert_match(/opclass: \{ description: :text_pattern_ops \}/, output) + end +end + +class SchemaIndexNullsOrderTest < ActiveRecord::PostgreSQLTestCase + include SchemaDumpingHelper + + setup do + @connection = ActiveRecord::Base.connection + @connection.create_table "trains" do |t| + t.string :name + t.text :description + end + end + + teardown do + @connection.drop_table "trains", if_exists: true + end + + def test_nulls_order_is_dumped + @connection.execute "CREATE INDEX trains_name_and_description ON trains USING btree(name NULLS FIRST, description)" + output = dump_table_schema "trains" + assert_match(/order: \{ name: "NULLS FIRST" \}/, output) + end + + def test_non_default_order_with_nulls_is_dumped + @connection.execute "CREATE INDEX trains_name_and_desc ON trains USING btree(name DESC NULLS LAST, description)" + output = dump_table_schema "trains" + assert_match(/order: \{ name: "DESC NULLS LAST" \}/, output) + end +end + class DefaultsUsingMultipleSchemasAndDomainTest < ActiveRecord::PostgreSQLTestCase setup do @connection = ActiveRecord::Base.connection @@ -531,7 +594,7 @@ class DefaultsUsingMultipleSchemasAndDomainTest < ActiveRecord::PostgreSQLTestCa end def test_decimal_defaults_in_new_schema_when_overriding_domain - assert_equal BigDecimal.new("3.14159265358979323846"), Default.new.decimal_col, "Default of decimal column was not correctly parsed" + assert_equal BigDecimal("3.14159265358979323846"), Default.new.decimal_col, "Default of decimal column was not correctly parsed" end def test_bpchar_defaults_in_new_schema_when_overriding_domain diff --git a/activerecord/test/cases/adapters/postgresql/serial_test.rb b/activerecord/test/cases/adapters/postgresql/serial_test.rb index d711b3b729..83ea86be6d 100644 --- a/activerecord/test/cases/adapters/postgresql/serial_test.rb +++ b/activerecord/test/cases/adapters/postgresql/serial_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/schema_dumping_helper" @@ -22,14 +24,14 @@ class PostgresqlSerialTest < ActiveRecord::PostgreSQLTestCase column = PostgresqlSerial.columns_hash["seq"] assert_equal :integer, column.type assert_equal "integer", column.sql_type - assert column.serial? + assert_predicate column, :serial? end def test_not_serial_column column = PostgresqlSerial.columns_hash["serials_id"] assert_equal :integer, column.type assert_equal "integer", column.sql_type - assert_not column.serial? + assert_not_predicate column, :serial? end def test_schema_dump_with_shorthand @@ -64,14 +66,14 @@ class PostgresqlBigSerialTest < ActiveRecord::PostgreSQLTestCase column = PostgresqlBigSerial.columns_hash["seq"] assert_equal :integer, column.type assert_equal "bigint", column.sql_type - assert column.serial? + assert_predicate column, :serial? end def test_not_bigserial_column column = PostgresqlBigSerial.columns_hash["serials_id"] assert_equal :integer, column.type assert_equal "bigint", column.sql_type - assert_not column.serial? + assert_not_predicate column, :serial? end def test_schema_dump_with_shorthand @@ -84,3 +86,71 @@ class PostgresqlBigSerialTest < ActiveRecord::PostgreSQLTestCase assert_match %r{t\.bigint\s+"serials_id",\s+default: -> \{ "nextval\('postgresql_big_serials_id_seq'::regclass\)" \}$}, output end end + +module SequenceNameDetectionTestCases + class CollidedSequenceNameTest < ActiveRecord::PostgreSQLTestCase + include SchemaDumpingHelper + + def setup + @connection = ActiveRecord::Base.connection + @connection.create_table :foo_bar, force: true do |t| + t.serial :baz_id + end + @connection.create_table :foo, force: true do |t| + t.serial :bar_id + t.bigserial :bar_baz_id + end + end + + def teardown + @connection.drop_table :foo_bar, if_exists: true + @connection.drop_table :foo, if_exists: true + end + + def test_serial_columns + columns = @connection.columns(:foo) + columns.each do |column| + assert_equal :integer, column.type + assert_predicate column, :serial? + end + end + + def test_schema_dump_with_collided_sequence_name + output = dump_table_schema "foo" + assert_match %r{t\.serial\s+"bar_id",\s+null: false$}, output + assert_match %r{t\.bigserial\s+"bar_baz_id",\s+null: false$}, output + end + end + + class LongerSequenceNameDetectionTest < ActiveRecord::PostgreSQLTestCase + include SchemaDumpingHelper + + def setup + @table_name = "long_table_name_to_test_sequence_name_detection_for_serial_cols" + @connection = ActiveRecord::Base.connection + @connection.create_table @table_name, force: true do |t| + t.serial :seq + t.bigserial :bigseq + end + end + + def teardown + @connection.drop_table @table_name, if_exists: true + end + + def test_serial_columns + columns = @connection.columns(@table_name) + columns.each do |column| + assert_equal :integer, column.type + assert_predicate column, :serial? + end + end + + def test_schema_dump_with_long_table_name + output = dump_table_schema @table_name + assert_match %r{create_table "#{@table_name}", force: :cascade}, output + assert_match %r{t\.serial\s+"seq",\s+null: false$}, output + assert_match %r{t\.bigserial\s+"bigseq",\s+null: false$}, output + end + end +end diff --git a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb index eb9978a898..fef4b02b04 100644 --- a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb +++ b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb @@ -1,15 +1,17 @@ +# frozen_string_literal: true + require "cases/helper" module ActiveRecord module ConnectionAdapters class PostgreSQLAdapter < AbstractAdapter - class InactivePGconn + class InactivePgConnection def query(*args) - raise PGError + raise PG::Error end def status - PGconn::CONNECTION_BAD + PG::CONNECTION_BAD end end @@ -21,7 +23,7 @@ module ActiveRecord assert_equal "bar", cache["foo"] pid = fork { - lookup = cache["foo"]; + lookup = cache["foo"] exit!(!lookup) } @@ -31,7 +33,7 @@ module ActiveRecord end def test_dealloc_does_not_raise_on_inactive_connection - cache = StatementPool.new InactivePGconn.new, 10 + cache = StatementPool.new InactivePgConnection.new, 10 cache["foo"] = "bar" assert_nothing_raised { cache.clear } end diff --git a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb index 962450aada..b7f213efc8 100644 --- a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb +++ b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/developer" require "models/topic" diff --git a/activerecord/test/cases/adapters/postgresql/transaction_test.rb b/activerecord/test/cases/adapters/postgresql/transaction_test.rb index 9b42d0383d..984b2f5ea4 100644 --- a/activerecord/test/cases/adapters/postgresql/transaction_test.rb +++ b/activerecord/test/cases/adapters/postgresql/transaction_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/connection_helper" require "concurrent/atomic/cyclic_barrier" @@ -12,6 +14,7 @@ module ActiveRecord setup do @abort, Thread.abort_on_exception = Thread.abort_on_exception, false + Thread.report_on_exception, @original_report_on_exception = false, Thread.report_on_exception @connection = ActiveRecord::Base.connection @@ -29,6 +32,7 @@ module ActiveRecord @connection.drop_table "samples", if_exists: true Thread.abort_on_exception = @abort + Thread.report_on_exception = @original_report_on_exception end test "raises SerializationFailure when a serialization failure occurs" do @@ -72,7 +76,7 @@ module ActiveRecord Sample.transaction do s1.lock! barrier.wait - s2.update_attributes value: 1 + s2.update value: 1 end end @@ -80,7 +84,7 @@ module ActiveRecord Sample.transaction do s2.lock! barrier.wait - s1.update_attributes value: 2 + s1.update value: 2 end ensure thread.join @@ -89,6 +93,90 @@ module ActiveRecord end end + test "raises LockWaitTimeout when lock wait timeout exceeded" do + skip unless ActiveRecord::Base.connection.postgresql_version >= 90300 + assert_raises(ActiveRecord::LockWaitTimeout) do + s = Sample.create!(value: 1) + latch1 = Concurrent::CountDownLatch.new + latch2 = Concurrent::CountDownLatch.new + + thread = Thread.new do + Sample.transaction do + Sample.lock.find(s.id) + latch1.count_down + latch2.wait + end + end + + begin + Sample.transaction do + latch1.wait + Sample.connection.execute("SET lock_timeout = 1") + Sample.lock.find(s.id) + end + ensure + Sample.connection.execute("SET lock_timeout = DEFAULT") + latch2.count_down + thread.join + end + end + end + + test "raises QueryCanceled when statement timeout exceeded" do + assert_raises(ActiveRecord::QueryCanceled) do + s = Sample.create!(value: 1) + latch1 = Concurrent::CountDownLatch.new + latch2 = Concurrent::CountDownLatch.new + + thread = Thread.new do + Sample.transaction do + Sample.lock.find(s.id) + latch1.count_down + latch2.wait + end + end + + begin + Sample.transaction do + latch1.wait + Sample.connection.execute("SET statement_timeout = 1") + Sample.lock.find(s.id) + end + ensure + Sample.connection.execute("SET statement_timeout = DEFAULT") + latch2.count_down + thread.join + end + end + end + + test "raises QueryCanceled when canceling statement due to user request" do + assert_raises(ActiveRecord::QueryCanceled) do + s = Sample.create!(value: 1) + latch = Concurrent::CountDownLatch.new + + thread = Thread.new do + Sample.transaction do + Sample.lock.find(s.id) + latch.count_down + sleep(0.5) + conn = Sample.connection + pid = conn.query_value("SELECT pid FROM pg_stat_activity WHERE query LIKE '% FOR UPDATE'") + conn.execute("SELECT pg_cancel_backend(#{pid})") + end + end + + begin + Sample.transaction do + latch.wait + Sample.lock.find(s.id) + end + ensure + thread.join + end + end + end + private def with_warning_suppression diff --git a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb index 784d77a8d1..8212ed4263 100644 --- a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb +++ b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" class PostgresqlTypeLookupTest < ActiveRecord::PostgreSQLTestCase @@ -6,16 +8,16 @@ class PostgresqlTypeLookupTest < ActiveRecord::PostgreSQLTestCase end test "array delimiters are looked up correctly" do - box_array = @connection.type_map.lookup(1020) - int_array = @connection.type_map.lookup(1007) + box_array = @connection.send(:type_map).lookup(1020) + int_array = @connection.send(:type_map).lookup(1007) assert_equal ";", box_array.delimiter assert_equal ",", int_array.delimiter end test "array types correctly respect registration of subtypes" do - int_array = @connection.type_map.lookup(1007, -1, "integer[]") - bigint_array = @connection.type_map.lookup(1016, -1, "bigint[]") + int_array = @connection.send(:type_map).lookup(1007, -1, "integer[]") + bigint_array = @connection.send(:type_map).lookup(1016, -1, "bigint[]") big_array = [123456789123456789] assert_raises(ActiveModel::RangeError) { int_array.serialize(big_array) } @@ -23,11 +25,11 @@ class PostgresqlTypeLookupTest < ActiveRecord::PostgreSQLTestCase end test "range types correctly respect registration of subtypes" do - int_range = @connection.type_map.lookup(3904, -1, "int4range") - bigint_range = @connection.type_map.lookup(3926, -1, "int8range") + int_range = @connection.send(:type_map).lookup(3904, -1, "int4range") + bigint_range = @connection.send(:type_map).lookup(3926, -1, "int8range") big_range = 0..123456789123456789 assert_raises(ActiveModel::RangeError) { int_range.serialize(big_range) } - assert_equal "[0,123456789123456789]", bigint_range.serialize(big_range) + assert_equal "[0,123456789123456789]", @connection.type_cast(bigint_range.serialize(big_range)) end end diff --git a/activerecord/test/cases/adapters/postgresql/utils_test.rb b/activerecord/test/cases/adapters/postgresql/utils_test.rb index 9f9e3bda2f..c91884f384 100644 --- a/activerecord/test/cases/adapters/postgresql/utils_test.rb +++ b/activerecord/test/cases/adapters/postgresql/utils_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "active_record/connection_adapters/postgresql/utils" diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index 6aa6a79705..71d07e2f4c 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/schema_dumping_helper" @@ -13,6 +15,10 @@ module PostgresqlUUIDHelper def uuid_function connection.supports_pgcrypto_uuid? ? "gen_random_uuid()" : "uuid_generate_v4()" end + + def uuid_default + connection.supports_pgcrypto_uuid? ? {} : { default: uuid_function } + end end class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase @@ -36,7 +42,8 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase drop_table "uuid_data_type" end - if ActiveRecord::Base.connection.supports_pgcrypto_uuid? + if ActiveRecord::Base.connection.respond_to?(:supports_pgcrypto_uuid?) && + ActiveRecord::Base.connection.supports_pgcrypto_uuid? def test_uuid_column_default connection.add_column :uuid_data_type, :thingy, :uuid, null: false, default: "gen_random_uuid()" UUIDType.reset_column_information @@ -59,14 +66,37 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase UUIDType.reset_column_information end + def test_add_column_with_null_true_and_default_nil + connection.add_column :uuid_data_type, :thingy, :uuid, null: true, default: nil + + UUIDType.reset_column_information + column = UUIDType.columns_hash["thingy"] + + assert column.null + assert_nil column.default + end + + def test_add_column_with_default_array + connection.add_column :uuid_data_type, :thingy, :uuid, array: true, default: [] + + UUIDType.reset_column_information + column = UUIDType.columns_hash["thingy"] + + assert_predicate column, :array? + assert_equal "{}", column.default + + schema = dump_table_schema "uuid_data_type" + assert_match %r{t\.uuid "thingy", default: \[\], array: true$}, schema + end + def test_data_type_of_uuid_types column = UUIDType.columns_hash["guid"] assert_equal :uuid, column.type assert_equal "uuid", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = UUIDType.type_for_attribute("guid") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_treat_blank_uuid_as_nil @@ -109,7 +139,9 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase "Z0000C99-9C0B-4EF8-BB6D-6BB9BD380A11", "a0eebc999r0b4ef8ab6d6bb9bd380a11", "a0ee-bc99------4ef8-bb6d-6bb9-bd38-0a11", - "{a0eebc99-bb6d6bb9-bd380a11}"].each do |invalid_uuid| + "{a0eebc99-bb6d6bb9-bd380a11}", + "{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11", + "a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}"].each do |invalid_uuid| uuid = UUIDType.new guid: invalid_uuid assert_nil uuid.guid end @@ -146,7 +178,7 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase duplicate = klass.new(guid: record.guid) assert record.guid.present? # Ensure we actually are testing a UUID - assert_not duplicate.valid? + assert_not_predicate duplicate, :valid? end end @@ -178,7 +210,7 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase t.uuid "other_uuid_2", default: "my_uuid_generator()" end - connection.create_table("pg_uuids_3", id: :uuid) do |t| + connection.create_table("pg_uuids_3", id: :uuid, **uuid_default) do |t| t.string "name" end end @@ -190,68 +222,66 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase connection.execute "DROP FUNCTION IF EXISTS my_uuid_generator();" end - if ActiveRecord::Base.connection.supports_extensions? - def test_id_is_uuid - assert_equal :uuid, UUID.columns_hash["id"].type - assert UUID.primary_key - end + def test_id_is_uuid + assert_equal :uuid, UUID.columns_hash["id"].type + assert UUID.primary_key + end - def test_id_has_a_default - u = UUID.create - assert_not_nil u.id - end + def test_id_has_a_default + u = UUID.create + assert_not_nil u.id + end - def test_auto_create_uuid - u = UUID.create - u.reload - assert_not_nil u.other_uuid - end + def test_auto_create_uuid + u = UUID.create + u.reload + assert_not_nil u.other_uuid + end - def test_pk_and_sequence_for_uuid_primary_key - pk, seq = connection.pk_and_sequence_for("pg_uuids") - assert_equal "id", pk - assert_nil seq - end + def test_pk_and_sequence_for_uuid_primary_key + pk, seq = connection.pk_and_sequence_for("pg_uuids") + assert_equal "id", pk + assert_nil seq + end - def test_schema_dumper_for_uuid_primary_key - schema = dump_table_schema "pg_uuids" - assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: -> { "uuid_generate_v1\(\)" }/, schema) - assert_match(/t\.uuid "other_uuid", default: -> { "uuid_generate_v4\(\)" }/, schema) - end + def test_schema_dumper_for_uuid_primary_key + schema = dump_table_schema "pg_uuids" + assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: -> { "uuid_generate_v1\(\)" }/, schema) + assert_match(/t\.uuid "other_uuid", default: -> { "uuid_generate_v4\(\)" }/, schema) + end + + def test_schema_dumper_for_uuid_primary_key_with_custom_default + schema = dump_table_schema "pg_uuids_2" + assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: -> { "my_uuid_generator\(\)" }/, schema) + assert_match(/t\.uuid "other_uuid_2", default: -> { "my_uuid_generator\(\)" }/, schema) + end - def test_schema_dumper_for_uuid_primary_key_with_custom_default - schema = dump_table_schema "pg_uuids_2" - assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: -> { "my_uuid_generator\(\)" }/, schema) - assert_match(/t\.uuid "other_uuid_2", default: -> { "my_uuid_generator\(\)" }/, schema) + def test_schema_dumper_for_uuid_primary_key_default + schema = dump_table_schema "pg_uuids_3" + if connection.supports_pgcrypto_uuid? + assert_match(/\bcreate_table "pg_uuids_3", id: :uuid, default: -> { "gen_random_uuid\(\)" }/, schema) + else + assert_match(/\bcreate_table "pg_uuids_3", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema) end + end + + def test_schema_dumper_for_uuid_primary_key_default_in_legacy_migration + @verbose_was = ActiveRecord::Migration.verbose + ActiveRecord::Migration.verbose = false - def test_schema_dumper_for_uuid_primary_key_default - schema = dump_table_schema "pg_uuids_3" - if connection.supports_pgcrypto_uuid? - assert_match(/\bcreate_table "pg_uuids_3", id: :uuid, default: -> { "gen_random_uuid\(\)" }/, schema) - else - assert_match(/\bcreate_table "pg_uuids_3", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema) + migration = Class.new(ActiveRecord::Migration[5.0]) do + def version; 101 end + def migrate(x) + create_table("pg_uuids_4", id: :uuid) end - end + end.new + ActiveRecord::Migrator.new(:up, [migration]).migrate - def test_schema_dumper_for_uuid_primary_key_default_in_legacy_migration - @verbose_was = ActiveRecord::Migration.verbose - ActiveRecord::Migration.verbose = false - - migration = Class.new(ActiveRecord::Migration[5.0]) do - def version; 101 end - def migrate(x) - create_table("pg_uuids_4", id: :uuid) - end - end.new - ActiveRecord::Migrator.new(:up, [migration]).migrate - - schema = dump_table_schema "pg_uuids_4" - assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema) - ensure - drop_table "pg_uuids_4" - ActiveRecord::Migration.verbose = @verbose_was - end + schema = dump_table_schema "pg_uuids_4" + assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema) + ensure + drop_table "pg_uuids_4" + ActiveRecord::Migration.verbose = @verbose_was end end @@ -270,38 +300,36 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::PostgreSQLTestCase drop_table "pg_uuids" end - if ActiveRecord::Base.connection.supports_extensions? - def test_id_allows_default_override_via_nil - col_desc = connection.execute("SELECT pg_get_expr(d.adbin, d.adrelid) as default - FROM pg_attribute a - LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum - WHERE a.attname='id' AND a.attrelid = 'pg_uuids'::regclass").first - assert_nil col_desc["default"] - end + def test_id_allows_default_override_via_nil + col_desc = connection.execute("SELECT pg_get_expr(d.adbin, d.adrelid) as default + FROM pg_attribute a + LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum + WHERE a.attname='id' AND a.attrelid = 'pg_uuids'::regclass").first + assert_nil col_desc["default"] + end - def test_schema_dumper_for_uuid_primary_key_with_default_override_via_nil - schema = dump_table_schema "pg_uuids" - assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: nil/, schema) - end + def test_schema_dumper_for_uuid_primary_key_with_default_override_via_nil + schema = dump_table_schema "pg_uuids" + assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: nil/, schema) + end - def test_schema_dumper_for_uuid_primary_key_with_default_nil_in_legacy_migration - @verbose_was = ActiveRecord::Migration.verbose - ActiveRecord::Migration.verbose = false - - migration = Class.new(ActiveRecord::Migration[5.0]) do - def version; 101 end - def migrate(x) - create_table("pg_uuids_4", id: :uuid, default: nil) - end - end.new - ActiveRecord::Migrator.new(:up, [migration]).migrate - - schema = dump_table_schema "pg_uuids_4" - assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: nil/, schema) - ensure - drop_table "pg_uuids_4" - ActiveRecord::Migration.verbose = @verbose_was - end + def test_schema_dumper_for_uuid_primary_key_with_default_nil_in_legacy_migration + @verbose_was = ActiveRecord::Migration.verbose + ActiveRecord::Migration.verbose = false + + migration = Class.new(ActiveRecord::Migration[5.0]) do + def version; 101 end + def migrate(x) + create_table("pg_uuids_4", id: :uuid, default: nil) + end + end.new + ActiveRecord::Migrator.new(:up, [migration]).migrate + + schema = dump_table_schema "pg_uuids_4" + assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: nil/, schema) + ensure + drop_table "pg_uuids_4" + ActiveRecord::Migration.verbose = @verbose_was end end @@ -320,10 +348,10 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase setup do connection.transaction do - connection.create_table("pg_uuid_posts", id: :uuid) do |t| + connection.create_table("pg_uuid_posts", id: :uuid, **uuid_default) do |t| t.string "title" end - connection.create_table("pg_uuid_comments", id: :uuid) do |t| + connection.create_table("pg_uuid_comments", id: :uuid, **uuid_default) do |t| t.references :uuid_post, type: :uuid t.string "content" end @@ -335,23 +363,21 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase drop_table "pg_uuid_posts" end - if ActiveRecord::Base.connection.supports_extensions? - def test_collection_association_with_uuid - post = UuidPost.create! - comment = post.uuid_comments.create! - assert post.uuid_comments.find(comment.id) - end + def test_collection_association_with_uuid + post = UuidPost.create! + comment = post.uuid_comments.create! + assert post.uuid_comments.find(comment.id) + end - def test_find_with_uuid - UuidPost.create! - assert_raise ActiveRecord::RecordNotFound do - UuidPost.find(123456) - end + def test_find_with_uuid + UuidPost.create! + assert_raise ActiveRecord::RecordNotFound do + UuidPost.find(123456) end + end - def test_find_by_with_uuid - UuidPost.create! - assert_nil UuidPost.find_by(id: 789) - end + def test_find_by_with_uuid + UuidPost.create! + assert_nil UuidPost.find_by(id: 789) end end diff --git a/activerecord/test/cases/adapters/postgresql/xml_test.rb b/activerecord/test/cases/adapters/postgresql/xml_test.rb index 826b384fb3..71ead6f7f3 100644 --- a/activerecord/test/cases/adapters/postgresql/xml_test.rb +++ b/activerecord/test/cases/adapters/postgresql/xml_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/schema_dumping_helper" diff --git a/activerecord/test/cases/adapters/sqlite3/collation_test.rb b/activerecord/test/cases/adapters/sqlite3/collation_test.rb index 28e8f12c18..76c8f7d8dd 100644 --- a/activerecord/test/cases/adapters/sqlite3/collation_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/collation_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "support/schema_dumping_helper" @@ -47,7 +49,7 @@ class SQLite3CollationTest < ActiveRecord::SQLite3TestCase test "schema dump includes collation" do output = dump_table_schema("collation_table_sqlite3") - assert_match %r{t.string\s+"string_nocase",\s+collation: "NOCASE"$}, output - assert_match %r{t.text\s+"text_rtrim",\s+collation: "RTRIM"$}, output + assert_match %r{t\.string\s+"string_nocase",\s+collation: "NOCASE"$}, output + assert_match %r{t\.text\s+"text_rtrim",\s+collation: "RTRIM"$}, output end end diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb index e1cfd703e8..ffb1d6afce 100644 --- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" class CopyTableTest < ActiveRecord::SQLite3TestCase diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb index 128acb79cf..b6d2ccdb53 100644 --- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb @@ -1,21 +1,23 @@ +# frozen_string_literal: true + require "cases/helper" -require "models/developer" -require "models/computer" +require "models/author" +require "models/post" class SQLite3ExplainTest < ActiveRecord::SQLite3TestCase - fixtures :developers + fixtures :authors def test_explain_for_one_query - explain = Developer.where(id: 1).explain - assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\? \[\["id", 1\]\]|1)), explain - assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain) + explain = Author.where(id: 1).explain + assert_match %r(EXPLAIN for: SELECT "authors"\.\* FROM "authors" WHERE "authors"\."id" = (?:\? \[\["id", 1\]\]|1)), explain + assert_match(/(SEARCH )?TABLE authors USING (INTEGER )?PRIMARY KEY/, explain) end def test_explain_with_eager_loading - explain = Developer.where(id: 1).includes(:audit_logs).explain - assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\? \[\["id", 1\]\]|1)), explain - assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain) - assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain - assert_match(/(SCAN )?TABLE audit_logs/, explain) + explain = Author.where(id: 1).includes(:posts).explain + assert_match %r(EXPLAIN for: SELECT "authors"\.\* FROM "authors" WHERE "authors"\."id" = (?:\? \[\["id", 1\]\]|1)), explain + assert_match(/(SEARCH )?TABLE authors USING (INTEGER )?PRIMARY KEY/, explain) + assert_match %r(EXPLAIN for: SELECT "posts"\.\* FROM "posts" WHERE "posts"\."author_id" = (?:\? \[\["author_id", 1\]\]|1)), explain + assert_match(/(SCAN )?TABLE posts/, explain) end end diff --git a/activerecord/test/cases/adapters/sqlite3/json_test.rb b/activerecord/test/cases/adapters/sqlite3/json_test.rb new file mode 100644 index 0000000000..6f247fcd22 --- /dev/null +++ b/activerecord/test/cases/adapters/sqlite3/json_test.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require "cases/helper" +require "cases/json_shared_test_cases" + +class SQLite3JSONTest < ActiveRecord::SQLite3TestCase + include JSONSharedTestCases + + def setup + super + @connection.create_table("json_data_type") do |t| + t.json "payload", default: {} + t.json "settings" + end + end + + def test_default + @connection.add_column "json_data_type", "permissions", column_type, default: { "users": "read", "posts": ["read", "write"] } + klass.reset_column_information + + assert_equal({ "users" => "read", "posts" => ["read", "write"] }, klass.column_defaults["permissions"]) + assert_equal({ "users" => "read", "posts" => ["read", "write"] }, klass.new.permissions) + end + + private + def column_type + :json + end +end diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index aefbb309e6..40b58e86bf 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "bigdecimal" require "securerandom" @@ -5,6 +7,11 @@ require "securerandom" class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase def setup @conn = ActiveRecord::Base.connection + @initial_represent_boolean_as_integer = ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer + end + + def teardown + ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = @initial_represent_boolean_as_integer end def test_type_cast_binary_encoding_without_logger @@ -15,15 +22,23 @@ class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase end def test_type_cast_true + ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = false assert_equal "t", @conn.type_cast(true) + + ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true + assert_equal 1, @conn.type_cast(true) end def test_type_cast_false + ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = false assert_equal "f", @conn.type_cast(false) + + ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true + assert_equal 0, @conn.type_cast(false) end def test_type_cast_bigdecimal - bd = BigDecimal.new "10.0" + bd = BigDecimal "10.0" assert_equal bd.to_f, @conn.type_cast(bd) end @@ -40,4 +55,37 @@ class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase assert_equal "'2000-01-01 12:30:00.999999'", @conn.quote(type.serialize(value)) end + + def test_quoted_time_normalizes_date_qualified_time + value = ::Time.utc(2018, 3, 11, 12, 30, 0, 999999) + type = ActiveRecord::Type::Time.new + + assert_equal "'2000-01-01 12:30:00.999999'", @conn.quote(type.serialize(value)) + end + + def test_quoted_time_dst_utc + with_env_tz "America/New_York" do + with_timezone_config default: :utc do + t = Time.new(2000, 7, 1, 0, 0, 0, "+04:30") + + expected = t.change(year: 2000, month: 1, day: 1) + expected = expected.getutc.to_s(:db).sub(/\A\d\d\d\d-\d\d-\d\d /, "2000-01-01 ") + + assert_equal expected, @conn.quoted_time(t) + end + end + end + + def test_quoted_time_dst_local + with_env_tz "America/New_York" do + with_timezone_config default: :local do + t = Time.new(2000, 7, 1, 0, 0, 0, "+04:30") + + expected = t.change(year: 2000, month: 1, day: 1) + expected = expected.getlocal.to_s(:db).sub(/\A\d\d\d\d-\d\d-\d\d /, "2000-01-01 ") + + assert_equal expected, @conn.quoted_time(t) + end + end + end end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index a6afb7816b..d1d4d545a3 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/owner" require "tempfile" @@ -49,22 +51,6 @@ module ActiveRecord end end - def test_valid_column - with_example_table do - column = @conn.columns("ex").find { |col| col.name == "id" } - assert @conn.valid_type?(column.type) - end - end - - # sqlite3 databases should be able to support any type and not just the - # ones mentioned in the native_database_types. - # - # Therefore test_invalid column should always return true even if the - # type is not valid. - def test_invalid_column - assert @conn.valid_type?(:foobar) - end - def test_column_types owner = Owner.create!(name: "hello".encode("ascii-8bit")) owner.reload @@ -75,7 +61,7 @@ module ActiveRecord WHERE #{Owner.primary_key} = #{owner.id} esql - assert(!result.rows.first.include?("blob"), "should not store blobs") + assert_not(result.rows.first.include?("blob"), "should not store blobs") ensure owner.delete end @@ -181,7 +167,7 @@ module ActiveRecord data binary ) eosql - str = "\x80".force_encoding("ASCII-8BIT") + str = "\x80".dup.force_encoding("ASCII-8BIT") binary = DualEncoding.new name: "いただきます!", data: str binary.save! assert_equal str, binary.data @@ -190,7 +176,7 @@ module ActiveRecord end def test_type_cast_should_not_mutate_encoding - name = "hello".force_encoding(Encoding::ASCII_8BIT) + name = "hello".dup.force_encoding(Encoding::ASCII_8BIT) Owner.create(name: name) assert_equal Encoding::ASCII_8BIT, name.encoding ensure @@ -276,27 +262,17 @@ module ActiveRecord def test_tables_logs_name sql = <<-SQL - SELECT name FROM sqlite_master - WHERE type = 'table' AND name <> 'sqlite_sequence' + SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence' AND type IN ('table') SQL assert_logged [[sql.squish, "SCHEMA", []]] do @conn.tables end end - def test_indexes_logs_name - with_example_table do - assert_logged [["PRAGMA index_list(\"ex\")", "SCHEMA", []]] do - assert_deprecated { @conn.indexes("ex", "hello") } - end - end - end - def test_table_exists_logs_name with_example_table do sql = <<-SQL - SELECT name FROM sqlite_master - WHERE type = 'table' AND name <> 'sqlite_sequence' AND name = 'ex' + SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence' AND name = 'ex' AND type IN ('table') SQL assert_logged [[sql.squish, "SCHEMA", []]] do assert @conn.table_exists?("ex") @@ -384,16 +360,126 @@ module ActiveRecord end end + class Barcode < ActiveRecord::Base + self.primary_key = "code" + end + + def test_copy_table_with_existing_records_have_custom_primary_key + connection = Barcode.connection + connection.create_table(:barcodes, primary_key: "code", id: :string, limit: 42, force: true) do |t| + t.text :other_attr + end + code = "214fe0c2-dd47-46df-b53b-66090b3c1d40" + Barcode.create!(code: code, other_attr: "xxx") + + connection.remove_column("barcodes", "other_attr") + + assert_equal code, Barcode.first.id + ensure + Barcode.reset_column_information + end + + def test_copy_table_with_composite_primary_keys + connection = Barcode.connection + connection.create_table(:barcodes, primary_key: ["region", "code"], force: true) do |t| + t.string :region + t.string :code + t.text :other_attr + end + region = "US" + code = "214fe0c2-dd47-46df-b53b-66090b3c1d40" + Barcode.create!(region: region, code: code, other_attr: "xxx") + + connection.remove_column("barcodes", "other_attr") + + assert_equal ["region", "code"], connection.primary_keys("barcodes") + + barcode = Barcode.first + assert_equal region, barcode.region + assert_equal code, barcode.code + ensure + Barcode.reset_column_information + end + + def test_custom_primary_key_in_create_table + connection = Barcode.connection + connection.create_table :barcodes, id: false, force: true do |t| + t.primary_key :id, :string + end + + assert_equal "id", connection.primary_key("barcodes") + + custom_pk = Barcode.columns_hash["id"] + + assert_equal :string, custom_pk.type + assert_not custom_pk.null + ensure + Barcode.reset_column_information + end + + def test_custom_primary_key_in_change_table + connection = Barcode.connection + connection.create_table :barcodes, id: false, force: true do |t| + t.integer :dummy + end + connection.change_table :barcodes do |t| + t.primary_key :id, :string + end + + assert_equal "id", connection.primary_key("barcodes") + + custom_pk = Barcode.columns_hash["id"] + + assert_equal :string, custom_pk.type + assert_not custom_pk.null + ensure + Barcode.reset_column_information + end + + def test_add_column_with_custom_primary_key + connection = Barcode.connection + connection.create_table :barcodes, id: false, force: true do |t| + t.integer :dummy + end + connection.add_column :barcodes, :id, :string, primary_key: true + + assert_equal "id", connection.primary_key("barcodes") + + custom_pk = Barcode.columns_hash["id"] + + assert_equal :string, custom_pk.type + assert_not custom_pk.null + ensure + Barcode.reset_column_information + end + + def test_remove_column_preserves_partial_indexes + connection = Barcode.connection + connection.create_table :barcodes, force: true do |t| + t.string :code + t.string :region + t.boolean :bool_attr + + t.index :code, unique: true, where: :bool_attr, name: "partial" + end + connection.remove_column :barcodes, :region + + index = connection.indexes("barcodes").find { |idx| idx.name == "partial" } + assert_equal "bool_attr", index.where + ensure + Barcode.reset_column_information + end + def test_supports_extensions assert_not @conn.supports_extensions?, "does not support extensions" end def test_respond_to_enable_extension - assert @conn.respond_to?(:enable_extension) + assert_respond_to @conn, :enable_extension end def test_respond_to_disable_extension - assert @conn.respond_to?(:disable_extension) + assert_respond_to @conn, :disable_extension end def test_statement_closed @@ -414,6 +500,43 @@ module ActiveRecord end end + def test_deprecate_valid_alter_table_type + assert_deprecated { @conn.valid_alter_table_type?(:string) } + end + + def test_db_is_not_readonly_when_readonly_option_is_false + conn = Base.sqlite3_connection database: ":memory:", + adapter: "sqlite3", + readonly: false + + assert_not_predicate conn.raw_connection, :readonly? + end + + def test_db_is_not_readonly_when_readonly_option_is_unspecified + conn = Base.sqlite3_connection database: ":memory:", + adapter: "sqlite3" + + assert_not_predicate conn.raw_connection, :readonly? + end + + def test_db_is_readonly_when_readonly_option_is_true + conn = Base.sqlite3_connection database: ":memory:", + adapter: "sqlite3", + readonly: true + + assert_predicate conn.raw_connection, :readonly? + end + + def test_writes_are_not_permitted_to_readonly_databases + conn = Base.sqlite3_connection database: ":memory:", + adapter: "sqlite3", + readonly: true + + assert_raises(ActiveRecord::StatementInvalid, /SQLite3::ReadOnlyException/) do + conn.execute("CREATE TABLE test(id integer)") + end + end + private def assert_logged(logs) diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb index b1b4463bf1..d70486605f 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" require "models/owner" diff --git a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb index 37ff973397..61002435a4 100644 --- a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cases/helper" class SQLite3StatementPoolTest < ActiveRecord::SQLite3TestCase @@ -8,7 +10,7 @@ class SQLite3StatementPoolTest < ActiveRecord::SQLite3TestCase assert_equal "bar", cache["foo"] pid = fork { - lookup = cache["foo"]; + lookup = cache["foo"] exit!(!lookup) } |