From 756cea1b6de7b6d6202f2517668a2165a0fb0d2f Mon Sep 17 00:00:00 2001 From: eileencodes Date: Wed, 19 Apr 2023 15:34:32 -0400 Subject: [PATCH 1/6] Fix database configuration and generation I tried using the same config for mysql2 and trilogy but instead it eneded up breaking the trilogy tests - they were only running in mysql2 mode. I wanted to do this so that the rake tasks for trilogy wouldn't need to be duplicated but since that didn't work out quite right, I've decide to duplicate the calls and add if exists / if not exists where applicable. The configs should be the same but this will make sure that if they do deviate, the dbs are always created/dropped. --- activerecord/Rakefile | 43 ++++++++----- activerecord/test/config.example.yml | 94 ++++++++++++++++++---------- 2 files changed, 88 insertions(+), 49 deletions(-) diff --git a/activerecord/Rakefile b/activerecord/Rakefile index fd9c0eafbc100..d9ce82741e6b7 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -209,11 +209,20 @@ end namespace :db do namespace :mysql do - connection_arguments = lambda do |connection_name| - config = ARTest.config["connections"]["mysql2"][connection_name] - ["--user=#{config["username"]}", ("--password=#{config["password"]}" if config["password"]), ("--host=#{config["host"]}" if config["host"]), ("--socket=#{config["socket"]}" if config["socket"])].join(" ") + mysql2_config = ARTest.config["connections"]["mysql2"] + mysql2_connection_arguments = lambda do |connection_name| + mysql2_connection = mysql2_config[connection_name] + ["--user=#{mysql2_connection["username"]}", ("--password=#{mysql2_connection["password"]}" if mysql2_connection["password"]), ("--host=#{mysql2_connection["host"]}" if mysql2_connection["host"]), ("--socket=#{mysql2_connection["socket"]}" if mysql2_connection["socket"])].join(" ") end + trilogy_config = ARTest.config["connections"]["trilogy"] + trilogy_connection_arguments = lambda do |connection_name| + trilogy_connection = trilogy_config[connection_name] + ["--user=#{trilogy_connection["username"]}", ("--password=#{trilogy_connection["password"]}" if trilogy_connection["password"]), ("--host=#{trilogy_connection["host"]}" if trilogy_connection["host"]), ("--socket=#{trilogy_connection["socket"]}" if trilogy_connection["socket"])].join(" ") + end + + mysql_configs = [mysql2_config, trilogy_config] + desc "Create the MySQL Rails User" task :build_user do if ENV["MYSQL_CODESPACES"] @@ -226,26 +235,30 @@ namespace :db do mysql_command = "mysql -uroot -e" end - config = ARTest.config["connections"]["mysql2"] - %x( #{mysql_command} "CREATE USER IF NOT EXISTS '#{config["arunit"]["username"]}'@'localhost';" ) - %x( #{mysql_command} "CREATE USER IF NOT EXISTS '#{config["arunit2"]["username"]}'@'localhost';" ) - %x( #{mysql_command} "GRANT ALL PRIVILEGES ON #{config["arunit"]["database"]}.* to '#{config["arunit"]["username"]}'@'localhost'" ) - %x( #{mysql_command} "GRANT ALL PRIVILEGES ON #{config["arunit2"]["database"]}.* to '#{config["arunit2"]["username"]}'@'localhost'" ) - %x( #{mysql_command} "GRANT ALL PRIVILEGES ON inexistent_activerecord_unittest.* to '#{config["arunit"]["username"]}'@'localhost';" ) + mysql_configs.each do |config| + %x( #{mysql_command} "CREATE USER IF NOT EXISTS '#{config["arunit"]["username"]}'@'localhost';" ) + %x( #{mysql_command} "CREATE USER IF NOT EXISTS '#{config["arunit2"]["username"]}'@'localhost';" ) + %x( #{mysql_command} "GRANT ALL PRIVILEGES ON #{config["arunit"]["database"]}.* to '#{config["arunit"]["username"]}'@'localhost'" ) + %x( #{mysql_command} "GRANT ALL PRIVILEGES ON #{config["arunit2"]["database"]}.* to '#{config["arunit2"]["username"]}'@'localhost'" ) + %x( #{mysql_command} "GRANT ALL PRIVILEGES ON inexistent_activerecord_unittest.* to '#{config["arunit"]["username"]}'@'localhost';" ) + end end desc "Build the MySQL test databases" task build: ["db:mysql:build_user"] do - config = ARTest.config["connections"]["mysql2"] - %x( mysql #{connection_arguments["arunit"]} -e "create DATABASE #{config["arunit"]["database"]} DEFAULT CHARACTER SET utf8mb4" ) - %x( mysql #{connection_arguments["arunit2"]} -e "create DATABASE #{config["arunit2"]["database"]} DEFAULT CHARACTER SET utf8mb4" ) + %x( mysql #{mysql2_connection_arguments["arunit"]} -e "create DATABASE IF NOT EXISTS #{mysql2_config["arunit"]["database"]} DEFAULT CHARACTER SET utf8mb4" ) + %x( mysql #{mysql2_connection_arguments["arunit2"]} -e "create DATABASE IF NOT EXISTS #{mysql2_config["arunit2"]["database"]} DEFAULT CHARACTER SET utf8mb4" ) + %x( mysql #{trilogy_connection_arguments["arunit"]} -e "create DATABASE IF NOT EXISTS #{trilogy_config["arunit"]["database"]} DEFAULT CHARACTER SET utf8mb4" ) + %x( mysql #{trilogy_connection_arguments["arunit2"]} -e "create DATABASE IF NOT EXISTS #{trilogy_config["arunit2"]["database"]} DEFAULT CHARACTER SET utf8mb4" ) end desc "Drop the MySQL test databases" task :drop do - config = ARTest.config["connections"]["mysql2"] - %x( mysqladmin #{connection_arguments["arunit"]} -f drop #{config["arunit"]["database"]} ) - %x( mysqladmin #{connection_arguments["arunit2"]} -f drop #{config["arunit2"]["database"]} ) + %x( mysql #{mysql2_connection_arguments["arunit"]} -e "drop database IF EXISTS #{mysql2_config["arunit"]["database"]}" ) + %x( mysql #{mysql2_connection_arguments["arunit2"]} -e "drop database IF EXISTS #{mysql2_config["arunit2"]["database"]}" ) + + %x( mysql #{trilogy_connection_arguments["arunit"]} -e "drop database IF EXISTS #{trilogy_config["arunit"]["database"]}" ) + %x( mysql #{trilogy_connection_arguments["arunit2"]} -e "drop database IF EXISTS #{trilogy_config["arunit2"]["database"]}" ) end desc "Rebuild the MySQL test databases" diff --git a/activerecord/test/config.example.yml b/activerecord/test/config.example.yml index f290a0b2eedc9..ae4c21acd70df 100644 --- a/activerecord/test/config.example.yml +++ b/activerecord/test/config.example.yml @@ -1,37 +1,5 @@ default_connection: <%= defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3' %> -mysql: &mysql - arunit: - username: rails - encoding: utf8mb4 - collation: utf8mb4_unicode_ci - <% if ENV['MYSQL_PREPARED_STATEMENTS'] %> - prepared_statements: true - <% else %> - prepared_statements: false - <% end %> - <% if ENV['MYSQL_HOST'] %> - host: <%= ENV['MYSQL_HOST'] %> - <% end %> - <% if ENV['MYSQL_SOCK'] %> - socket: "<%= ENV['MYSQL_SOCK'] %>" - <% end %> - arunit2: - username: rails - encoding: utf8mb4 - collation: utf8mb4_general_ci - <% if ENV['MYSQL_PREPARED_STATEMENTS'] %> - prepared_statements: true - <% else %> - prepared_statements: false - <% end %> - <% if ENV['MYSQL_HOST'] %> - host: <%= ENV['MYSQL_HOST'] %> - <% end %> - <% if ENV['MYSQL_SOCK'] %> - socket: "<%= ENV['MYSQL_SOCK'] %>" - <% end %> - connections: jdbcderby: arunit: activerecord_unittest @@ -68,7 +36,36 @@ connections: timeout: 5000 mysql2: - <<: *mysql + arunit: + username: rails + encoding: utf8mb4 + collation: utf8mb4_unicode_ci + <% if ENV['MYSQL_PREPARED_STATEMENTS'] %> + prepared_statements: true + <% else %> + prepared_statements: false + <% end %> + <% if ENV['MYSQL_HOST'] %> + host: <%= ENV['MYSQL_HOST'] %> + <% end %> + <% if ENV['MYSQL_SOCK'] %> + socket: "<%= ENV['MYSQL_SOCK'] %>" + <% end %> + arunit2: + username: rails + encoding: utf8mb4 + collation: utf8mb4_general_ci + <% if ENV['MYSQL_PREPARED_STATEMENTS'] %> + prepared_statements: true + <% else %> + prepared_statements: false + <% end %> + <% if ENV['MYSQL_HOST'] %> + host: <%= ENV['MYSQL_HOST'] %> + <% end %> + <% if ENV['MYSQL_SOCK'] %> + socket: "<%= ENV['MYSQL_SOCK'] %>" + <% end %> oracle: arunit: @@ -112,4 +109,33 @@ connections: database: ':memory:' trilogy: - <<: *mysql + arunit: + username: rails + encoding: utf8mb4 + collation: utf8mb4_unicode_ci + <% if ENV['MYSQL_PREPARED_STATEMENTS'] %> + prepared_statements: true + <% else %> + prepared_statements: false + <% end %> + <% if ENV['MYSQL_HOST'] %> + host: <%= ENV['MYSQL_HOST'] %> + <% end %> + <% if ENV['MYSQL_SOCK'] %> + socket: "<%= ENV['MYSQL_SOCK'] %>" + <% end %> + arunit2: + username: rails + encoding: utf8mb4 + collation: utf8mb4_general_ci + <% if ENV['MYSQL_PREPARED_STATEMENTS'] %> + prepared_statements: true + <% else %> + prepared_statements: false + <% end %> + <% if ENV['MYSQL_HOST'] %> + host: <%= ENV['MYSQL_HOST'] %> + <% end %> + <% if ENV['MYSQL_SOCK'] %> + socket: "<%= ENV['MYSQL_SOCK'] %>" + <% end %> From 7572a13af6444abf51720051c6a93d72a7aead0e Mon Sep 17 00:00:00 2001 From: eileencodes Date: Wed, 19 Apr 2023 15:45:19 -0400 Subject: [PATCH 2/6] Raise an error if the connection name doesn't match the adapter This will prevent us from accidentally running mysql2 tests against trilogy adapter as we currently are doing. --- activerecord/test/support/connection.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb index af2f3c8a496a4..d6b8097adf1f2 100644 --- a/activerecord/test/support/connection.rb +++ b/activerecord/test/support/connection.rb @@ -25,5 +25,12 @@ def self.connect ActiveRecord::Base.configurations = test_configuration_hashes ActiveRecord::Base.establish_connection :arunit ARUnit2Model.establish_connection :arunit2 + + arunit_adapter = ActiveRecord::Base.connection.pool.db_config.adapter + + if connection_name != arunit_adapter + return if connection_name == "sqlite3_mem" && arunit_adapter == "sqlite3" + raise ArgumentError, "The connection name did not match the adapter name. Connection name is '#{connection_name}' and the adapter name is '#{arunit_adapter}'." + end end end From 14f22758ccd1b731cbef72945184541bbc7fafa0 Mon Sep 17 00:00:00 2001 From: eileencodes Date: Wed, 19 Apr 2023 16:46:10 -0400 Subject: [PATCH 3/6] Bump trilogy to fix connection error translation Until we do a new release we need to run against main on trilogy to get the latest changes from https://github.com/github/trilogy/pull/69 --- Gemfile | 2 +- Gemfile.lock | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index 6bdfb77890d98..aa7c6a2784560 100644 --- a/Gemfile +++ b/Gemfile @@ -150,7 +150,7 @@ platforms :ruby, :windows do group :db do gem "pg", "~> 1.3" gem "mysql2", "~> 0.5" - gem "trilogy", "~> 2.4" + gem "trilogy", github: "github/trilogy", branch: "main", glob: "contrib/ruby/*.gemspec" end end diff --git a/Gemfile.lock b/Gemfile.lock index 2ac26dc76b569..46c8777333280 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,11 @@ +GIT + remote: https://github.com/github/trilogy.git + revision: 8e4ae98569c12894da9bcbee5edb32b76068dbfd + branch: main + glob: contrib/ruby/*.gemspec + specs: + trilogy (2.4.0) + GIT remote: https://github.com/matthewd/websocket-client-simple.git revision: e161305f1a466b9398d86df3b1731b03362da91b @@ -338,9 +346,13 @@ GEM net-smtp (0.3.3) net-protocol nio4r (2.5.8) - nokogiri (1.13.10) + nokogiri (1.14.3) mini_portile2 (~> 2.8.0) racc (~> 1.4) + nokogiri (1.14.3-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.14.3-x86_64-linux) + racc (~> 1.4) os (1.1.4) parallel (1.22.1) parser (3.2.1.1) @@ -486,8 +498,10 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - sqlite3 (1.5.4) + sqlite3 (1.6.2) mini_portile2 (~> 2.8.0) + sqlite3 (1.6.2-x86_64-darwin) + sqlite3 (1.6.2-x86_64-linux) stackprof (0.2.23) stimulus-rails (1.2.1) railties (>= 6.0.0) @@ -507,7 +521,6 @@ GEM timeout (0.3.2) tomlrb (2.0.3) trailblazer-option (0.1.2) - trilogy (2.4.0) turbo-rails (1.3.2) actionpack (>= 6.0.0) activejob (>= 6.0.0) @@ -619,7 +632,7 @@ DEPENDENCIES sucker_punch tailwindcss-rails terser (>= 1.1.4) - trilogy (~> 2.4) + trilogy! turbo-rails tzinfo-data w3c_validators (~> 1.3.6) From 58a0a52019ff0066f8ee072ce0a2a02b20422393 Mon Sep 17 00:00:00 2001 From: eileencodes Date: Thu, 20 Apr 2023 09:00:19 -0400 Subject: [PATCH 4/6] Fix config and copy over trilogy schema We had a mysql2_specific_schema.rb file but not one for trilogy that was causing tests to break. I also removed the prepared statements from trilogy because it is not supported. --- activerecord/test/config.example.yml | 8 -- .../test/schema/trilogy_specific_schema.rb | 84 +++++++++++++++++++ 2 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 activerecord/test/schema/trilogy_specific_schema.rb diff --git a/activerecord/test/config.example.yml b/activerecord/test/config.example.yml index ae4c21acd70df..532651bddce38 100644 --- a/activerecord/test/config.example.yml +++ b/activerecord/test/config.example.yml @@ -113,11 +113,7 @@ connections: username: rails encoding: utf8mb4 collation: utf8mb4_unicode_ci - <% if ENV['MYSQL_PREPARED_STATEMENTS'] %> - prepared_statements: true - <% else %> prepared_statements: false - <% end %> <% if ENV['MYSQL_HOST'] %> host: <%= ENV['MYSQL_HOST'] %> <% end %> @@ -128,11 +124,7 @@ connections: username: rails encoding: utf8mb4 collation: utf8mb4_general_ci - <% if ENV['MYSQL_PREPARED_STATEMENTS'] %> - prepared_statements: true - <% else %> prepared_statements: false - <% end %> <% if ENV['MYSQL_HOST'] %> host: <%= ENV['MYSQL_HOST'] %> <% end %> diff --git a/activerecord/test/schema/trilogy_specific_schema.rb b/activerecord/test/schema/trilogy_specific_schema.rb new file mode 100644 index 0000000000000..cb99f66f61536 --- /dev/null +++ b/activerecord/test/schema/trilogy_specific_schema.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +ActiveRecord::Schema.define do + if connection.supports_datetime_with_precision? + create_table :datetime_defaults, force: true do |t| + t.datetime :modified_datetime, precision: nil, default: -> { "CURRENT_TIMESTAMP" } + t.datetime :precise_datetime, default: -> { "CURRENT_TIMESTAMP(6)" } + t.datetime :updated_datetime, default: -> { "CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)" } + end + + create_table :timestamp_defaults, force: true do |t| + t.timestamp :nullable_timestamp + t.timestamp :modified_timestamp, precision: nil, default: -> { "CURRENT_TIMESTAMP" } + t.timestamp :precise_timestamp, precision: 6, default: -> { "CURRENT_TIMESTAMP(6)" } + t.timestamp :updated_timestamp, precision: 6, default: -> { "CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)" } + end + end + + create_table :defaults, force: true do |t| + t.date :fixed_date, default: "2004-01-01" + t.datetime :fixed_time, default: "2004-01-01 00:00:00" + t.column :char1, "char(1)", default: "Y" + t.string :char2, limit: 50, default: "a varchar field" + if ActiveRecord::TestCase.supports_default_expression? + t.binary :uuid, limit: 36, default: -> { "(uuid())" } + end + end + + create_table :binary_fields, force: true do |t| + t.binary :var_binary, limit: 255 + t.binary :var_binary_large, limit: 4095 + + t.tinyblob :tiny_blob + t.blob :normal_blob + t.mediumblob :medium_blob + t.longblob :long_blob + t.tinytext :tiny_text + t.text :normal_text + t.mediumtext :medium_text + t.longtext :long_text + + t.binary :tiny_blob_2, size: :tiny + t.binary :medium_blob_2, size: :medium + t.binary :long_blob_2, size: :long + t.text :tiny_text_2, size: :tiny + t.text :medium_text_2, size: :medium + t.text :long_text_2, size: :long + + t.index :var_binary + end + + create_table :key_tests, force: true, options: "CHARSET=utf8 ENGINE=MyISAM" do |t| + t.string :awesome + t.string :pizza + t.string :snacks + t.index :awesome, type: :fulltext, name: "index_key_tests_on_awesome" + t.index :pizza, using: :btree, name: "index_key_tests_on_pizza" + t.index :snacks, name: "index_key_tests_on_snack" + end + + create_table :collation_tests, id: false, force: true do |t| + t.string :string_cs_column, limit: 1, collation: "utf8mb4_bin" + t.string :string_ci_column, limit: 1, collation: "utf8mb4_general_ci" + t.binary :binary_column, limit: 1 + end + + execute "DROP PROCEDURE IF EXISTS ten" + + execute <<~SQL + CREATE PROCEDURE ten() SQL SECURITY INVOKER + BEGIN + SELECT 10; + END + SQL + + execute "DROP PROCEDURE IF EXISTS topics" + + execute <<~SQL + CREATE PROCEDURE topics(IN num INT) SQL SECURITY INVOKER + BEGIN + SELECT * FROM topics LIMIT num; + END + SQL +end From 8935db5458577c49a9524a0b961de0d783fcefdf Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 21 Apr 2023 10:03:31 +0200 Subject: [PATCH 5/6] TrilogyAdapterTest: use the proper config --- .../test/cases/adapters/trilogy/trilogy_adapter_test.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/activerecord/test/cases/adapters/trilogy/trilogy_adapter_test.rb b/activerecord/test/cases/adapters/trilogy/trilogy_adapter_test.rb index aaf0e8c328a02..615e39262ebd1 100644 --- a/activerecord/test/cases/adapters/trilogy/trilogy_adapter_test.rb +++ b/activerecord/test/cases/adapters/trilogy/trilogy_adapter_test.rb @@ -9,11 +9,7 @@ class TrilogyAdapterTest < ActiveRecord::TrilogyTestCase setup do - @configuration = { - adapter: "trilogy", - username: "rails", - database: "activerecord_unittest", - } + @configuration = ARTest.config.fetch("connections").fetch("trilogy").fetch("arunit").symbolize_keys @adapter = trilogy_adapter @adapter.execute("TRUNCATE books") From 5aabcba52bd8daf6d650aa98bf09f15acd182088 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 24 Apr 2023 09:00:06 +0200 Subject: [PATCH 6/6] Delete incompatible Trilogy tests The trilogy test suite was imported directly, but many of the test can't work as is in Rails test suite because they were mutating DB state. Most are redundant anyway, or should be implemented as shared test that are executed for all adapters. --- .../adapters/trilogy/trilogy_adapter_test.rb | 633 ++---------------- .../test/cases/json_attribute_test.rb | 1 + 2 files changed, 61 insertions(+), 573 deletions(-) diff --git a/activerecord/test/cases/adapters/trilogy/trilogy_adapter_test.rb b/activerecord/test/cases/adapters/trilogy/trilogy_adapter_test.rb index 615e39262ebd1..8aeffd5db3870 100644 --- a/activerecord/test/cases/adapters/trilogy/trilogy_adapter_test.rb +++ b/activerecord/test/cases/adapters/trilogy/trilogy_adapter_test.rb @@ -9,555 +9,151 @@ class TrilogyAdapterTest < ActiveRecord::TrilogyTestCase setup do - @configuration = ARTest.config.fetch("connections").fetch("trilogy").fetch("arunit").symbolize_keys - - @adapter = trilogy_adapter - @adapter.execute("TRUNCATE books") - @adapter.execute("TRUNCATE posts") - - db_config = ActiveRecord::DatabaseConfigurations.new({}).resolve(@configuration) - pool_config = ActiveRecord::ConnectionAdapters::PoolConfig.new(ActiveRecord::Base, db_config, :writing, :default) - @pool = ActiveRecord::ConnectionAdapters::ConnectionPool.new(pool_config) - end - - teardown do - @adapter.disconnect! + @conn = ActiveRecord::Base.connection end test "#explain for one query" do - explain = @adapter.explain("select * from posts") + explain = @conn.explain("select * from posts") assert_match %(possible_keys), explain end - test "#default_prepared_statements" do - assert_not_predicate @pool.connection, :prepared_statements? - end - test "#adapter_name answers name" do - assert_equal "Trilogy", @adapter.adapter_name + assert_equal "Trilogy", @conn.adapter_name end test "#supports_json answers true without Maria DB and greater version" do - assert @adapter.supports_json? + assert @conn.supports_json? end test "#supports_json answers false without Maria DB and lesser version" do - database_version = @adapter.class::Version.new("5.0.0", nil) + database_version = @conn.class::Version.new("5.0.0", nil) - @adapter.stub(:database_version, database_version) do - assert_equal false, @adapter.supports_json? + @conn.stub(:database_version, database_version) do + assert_equal false, @conn.supports_json? end end test "#supports_json answers false with Maria DB" do - @adapter.stub(:mariadb?, true) do - assert_equal false, @adapter.supports_json? + @conn.stub(:mariadb?, true) do + assert_equal false, @conn.supports_json? end end test "#supports_comments? answers true" do - assert @adapter.supports_comments? + assert @conn.supports_comments? end test "#supports_comments_in_create? answers true" do - assert @adapter.supports_comments_in_create? + assert @conn.supports_comments_in_create? end test "#supports_savepoints? answers true" do - assert @adapter.supports_savepoints? + assert @conn.supports_savepoints? end test "#requires_reloading? answers false" do - assert_equal false, @adapter.requires_reloading? + assert_equal false, @conn.requires_reloading? end test "#native_database_types answers known types" do - assert_equal ActiveRecord::ConnectionAdapters::TrilogyAdapter::NATIVE_DATABASE_TYPES, @adapter.native_database_types + assert_equal ActiveRecord::ConnectionAdapters::TrilogyAdapter::NATIVE_DATABASE_TYPES, @conn.native_database_types end test "#quote_column_name answers quoted string when not quoted" do - assert_equal "`test`", @adapter.quote_column_name("test") + assert_equal "`test`", @conn.quote_column_name("test") end test "#quote_column_name answers triple quoted string when quoted" do - assert_equal "```test```", @adapter.quote_column_name("`test`") + assert_equal "```test```", @conn.quote_column_name("`test`") end test "#quote_column_name answers quoted string for integer" do - assert_equal "`1`", @adapter.quote_column_name(1) + assert_equal "`1`", @conn.quote_column_name(1) end test "#quote_string answers string with connection" do - assert_equal "\\\"test\\\"", @adapter.quote_string(%("test")) - end - - test "#quote_string works when the connection is known to be closed" do - adapter = trilogy_adapter - adapter.connect! - adapter.instance_variable_get(:@raw_connection).close - - assert_equal "\\\"test\\\"", adapter.quote_string(%("test")) + assert_equal "\\\"test\\\"", @conn.quote_string(%("test")) end test "#quoted_true answers TRUE" do - assert_equal "TRUE", @adapter.quoted_true + assert_equal "TRUE", @conn.quoted_true end test "#quoted_false answers FALSE" do - assert_equal "FALSE", @adapter.quoted_false + assert_equal "FALSE", @conn.quoted_false end test "#active? answers true with connection" do - assert @adapter.active? + assert @conn.active? end test "#active? answers false with connection and exception" do - @adapter.send(:connection).stub(:ping, -> { raise ::Trilogy::BaseError.new }) do - assert_equal false, @adapter.active? - end - end - - test "#active? answers false without connection" do - adapter = trilogy_adapter - assert_equal false, adapter.active? - end - - test "#reconnect closes connection with connection" do - connection = Minitest::Mock.new Trilogy.new(@configuration) - connection.expect :close, true - adapter = trilogy_adapter_with_connection(connection) - adapter.reconnect! - - assert connection.verify - end - - test "#reconnect doesn't retain old connection on failure" do - old_connection = Minitest::Mock.new Trilogy.new(@configuration) - old_connection.expect :close, true - - adapter = trilogy_adapter_with_connection(old_connection) - - begin - Trilogy.stub(:new, -> _ { raise Trilogy::BaseError.new }) do - adapter.reconnect! - end - rescue ActiveRecord::StatementInvalid => ex - assert_instance_of Trilogy::BaseError, ex.cause - else - flunk "Expected Trilogy::BaseError to be raised" + @conn.send(:connection).stub(:ping, -> { raise ::Trilogy::BaseError.new }) do + assert_equal false, @conn.active? end - - assert_nil adapter.send(:connection) end test "#reconnect answers new connection with existing connection" do - old_connection = @adapter.send(:connection) - @adapter.reconnect! - connection = @adapter.send(:connection) + old_connection = @conn.send(:connection) + @conn.reconnect! + connection = @conn.send(:connection) assert_instance_of Trilogy, connection assert_not_equal old_connection, connection end - test "#reconnect answers new connection without existing connection" do - adapter = trilogy_adapter - adapter.reconnect! - assert_instance_of Trilogy, adapter.send(:connection) - end - - test "#reset closes connection with existing connection" do - connection = Minitest::Mock.new Trilogy.new(@configuration) - connection.expect :close, true - adapter = trilogy_adapter_with_connection(connection) - adapter.reset! - - assert connection.verify - end - test "#reset answers new connection with existing connection" do - old_connection = @adapter.send(:connection) - @adapter.reset! - connection = @adapter.send(:connection) + old_connection = @conn.send(:connection) + @conn.reset! + connection = @conn.send(:connection) assert_instance_of Trilogy, connection assert_not_equal old_connection, connection end - test "#reset answers new connection without existing connection" do - adapter = trilogy_adapter - adapter.reset! - assert_instance_of Trilogy, adapter.send(:connection) - end - - test "#disconnect closes connection with existing connection" do - connection = Minitest::Mock.new Trilogy.new(@configuration) - connection.expect :close, true - adapter = trilogy_adapter_with_connection(connection) - adapter.disconnect! - - assert connection.verify - end - test "#disconnect makes adapter inactive with connection" do - @adapter.disconnect! - assert_equal false, @adapter.active? + @conn.disconnect! + assert_equal false, @conn.active? end test "#disconnect answers nil with connection" do - assert_nil @adapter.disconnect! - end - - test "#disconnect answers nil without connection" do - adapter = trilogy_adapter - assert_nil adapter.disconnect! - end - - test "#disconnect leaves adapter inactive without connection" do - adapter = trilogy_adapter - adapter.disconnect! - - assert_equal false, adapter.active? + assert_nil @conn.disconnect! end test "#discard answers nil with connection" do - assert_nil @adapter.discard! + assert_nil @conn.discard! end test "#discard makes adapter inactive with connection" do - @adapter.discard! - assert_equal false, @adapter.active? - end - - test "#discard answers nil without connection" do - adapter = trilogy_adapter - assert_nil adapter.discard! - end - - test "#exec_query answers result with valid query" do - result = @adapter.exec_query "SELECT id, author_id, title, body FROM posts;" - - assert_equal %w[id author_id title body], result.columns - assert_equal [], result.rows + @conn.discard! + assert_equal false, @conn.active? end test "#exec_query fails with invalid query" do assert_raises_with_message ActiveRecord::StatementInvalid, /'activerecord_unittest.bogus' doesn't exist/ do - @adapter.exec_query "SELECT * FROM bogus;" - end - end - - test "#exec_insert inserts new row" do - @adapter.exec_insert "INSERT INTO posts (title, body) VALUES ('Test', 'example');", nil, nil - result = @adapter.execute "SELECT id, title, body FROM posts;" - - assert_equal [[1, "Test", "example"]], result.rows - end - - test "#exec_delete deletes existing row" do - @adapter.execute "INSERT INTO posts (title, body) VALUES ('Test', 'example');" - @adapter.exec_delete "DELETE FROM posts WHERE title = 'Test';", nil, nil - result = @adapter.execute "SELECT id, title, body FROM posts;" - - assert_equal [], result.rows - end - - test "#exec_update updates existing row" do - @adapter.execute "INSERT INTO posts (title, body) VALUES ('Test', 'example');" - @adapter.exec_update "UPDATE posts SET title = 'Test II' where body = 'example';", nil, nil - result = @adapter.execute "SELECT id, title, body FROM posts;" - - assert_equal [[1, "Test II", "example"]], result.rows - end - - test "default query flags set timezone to UTC" do - if ActiveRecord.respond_to?(:default_timezone) - assert_equal :utc, ActiveRecord.default_timezone - else - assert_equal :utc, ActiveRecord::Base.default_timezone - end - ruby_time = Time.utc(2019, 5, 31, 12, 52) - time = "2019-05-31 12:52:00" - - @adapter.execute("INSERT into books (name, format, created_at, updated_at) VALUES ('name', 'paperback', '#{time}', '#{time}');") - result = @adapter.execute("select * from books limit 1;") - - result.each_hash do |hsh| - assert_equal ruby_time, hsh["created_at"] - assert_equal ruby_time, hsh["updated_at"] - end - - assert_equal 1, @adapter.send(:connection).query_flags - end - - test "query flags for timezone can be set to local" do - if ActiveRecord.respond_to?(:default_timezone) - old_timezone, ActiveRecord.default_timezone = ActiveRecord.default_timezone, :local - assert_equal :local, ActiveRecord.default_timezone - else - old_timezone, ActiveRecord::Base.default_timezone = ActiveRecord::Base.default_timezone, :local - assert_equal :local, ActiveRecord::Base.default_timezone - end - ruby_time = Time.local(2019, 5, 31, 12, 52) - time = "2019-05-31 12:52:00" - - @adapter.execute("INSERT into books (name, format, created_at, updated_at) VALUES ('name', 'paperback', '#{time}', '#{time}');") - result = @adapter.execute("select * from books limit 1;") - - result.each_hash do |hsh| - assert_equal ruby_time, hsh["created_at"] - assert_equal ruby_time, hsh["updated_at"] - end - - assert_equal 5, @adapter.send(:connection).query_flags - ensure - if ActiveRecord.respond_to?(:default_timezone) - ActiveRecord.default_timezone = old_timezone - else - ActiveRecord::Base.default_timezone = old_timezone - end - end - - test "query flags for timezone can be set to local and reset to utc" do - if ActiveRecord.respond_to?(:default_timezone) - old_timezone, ActiveRecord.default_timezone = ActiveRecord.default_timezone, :local - assert_equal :local, ActiveRecord.default_timezone - else - old_timezone, ActiveRecord::Base.default_timezone = ActiveRecord::Base.default_timezone, :local - assert_equal :local, ActiveRecord::Base.default_timezone - end - ruby_time = Time.local(2019, 5, 31, 12, 52) - time = "2019-05-31 12:52:00" - - @adapter.execute("INSERT into books (name, format, created_at, updated_at) VALUES ('name', 'paperback', '#{time}', '#{time}');") - result = @adapter.execute("select * from books limit 1;") - - result.each_hash do |hsh| - assert_equal ruby_time, hsh["created_at"] - assert_equal ruby_time, hsh["updated_at"] - end - - assert_equal 5, @adapter.send(:connection).query_flags - - if ActiveRecord.respond_to?(:default_timezone) - ActiveRecord.default_timezone = :utc - else - ActiveRecord::Base.default_timezone = :utc - end - - ruby_utc_time = Time.utc(2019, 5, 31, 12, 52) - utc_result = @adapter.execute("select * from books limit 1;") - - utc_result.each_hash do |hsh| - assert_equal ruby_utc_time, hsh["created_at"] - assert_equal ruby_utc_time, hsh["updated_at"] - end - - assert_equal 1, @adapter.send(:connection).query_flags - ensure - if ActiveRecord.respond_to?(:default_timezone) - ActiveRecord.default_timezone = old_timezone - else - ActiveRecord::Base.default_timezone = old_timezone + @conn.exec_query "SELECT * FROM bogus;" end end test "#execute answers results for valid query" do - result = @adapter.execute "SELECT id, author_id, title, body FROM posts;" - assert_equal %w[id author_id title body], result.fields - end - - test "#execute answers results for valid query after reconnect" do - mock_connection = Minitest::Mock.new Trilogy.new(@configuration) - adapter = trilogy_adapter_with_connection(mock_connection) - - # Cause an ER_SERVER_SHUTDOWN error (code 1053) after the session is - # set. On reconnect, the adapter will get a real, working connection. - server_shutdown_error = Trilogy::ProtocolError.new - server_shutdown_error.instance_variable_set(:@error_code, 1053) - mock_connection.expect(:query, nil) { raise server_shutdown_error } - - assert_raises(ActiveRecord::ConnectionFailed) do - adapter.execute "SELECT * FROM posts;" - end - - adapter.reconnect! - result = adapter.execute "SELECT id, author_id, title, body FROM posts;" - + result = @conn.execute "SELECT id, author_id, title, body FROM posts;" assert_equal %w[id author_id title body], result.fields - assert mock_connection.verify - mock_connection.close end test "#execute fails with invalid query" do assert_raises_with_message ActiveRecord::StatementInvalid, /Table 'activerecord_unittest.bogus' doesn't exist/ do - @adapter.execute "SELECT * FROM bogus;" + @conn.execute "SELECT * FROM bogus;" end end test "#execute fails with invalid SQL" do assert_raises(ActiveRecord::StatementInvalid) do - @adapter.execute "SELECT bogus FROM posts;" - end - end - - test "#execute answers results for valid query after losing connection unexpectedly" do - connection = Trilogy.new(@configuration.merge(read_timeout: 1)) - - adapter = trilogy_adapter_with_connection(connection) - assert adapter.active? - - # Make connection lost for future queries by exceeding the read timeout - assert_raises(Trilogy::TimeoutError) do - connection.query "SELECT sleep(2);" - end - assert_not adapter.active? - - # The adapter believes the connection is verified, so it will run the - # following query immediately. It will fail, and as the query's not - # retryable, the adapter will raise an error. - - # The next query fails because the connection is lost - assert_raises(ActiveRecord::ConnectionFailed) do - adapter.execute "SELECT COUNT(*) FROM posts;" - end - assert_not adapter.active? - - # The adapter now knows the connection is lost, so it will re-verify (and - # ultimately reconnect) before running another query. - - # This query triggers a reconnect - result = adapter.execute "SELECT COUNT(*) FROM posts;" - assert_equal [[0]], result.rows - assert adapter.active? - end - - test "#execute answers results for valid query after losing connection" do - connection = Trilogy.new(@configuration.merge(read_timeout: 1)) - - adapter = trilogy_adapter_with_connection(connection) - assert adapter.active? - - # Make connection lost for future queries by exceeding the read timeout - assert_raises(ActiveRecord::StatementInvalid) do - adapter.execute "SELECT sleep(2);" - end - assert_not adapter.active? - - # The above failure has not yet caused a reconnect, but the adapter has - # lost confidence in the connection, so it will re-verify before running - # the next query -- which means it will succeed. - - # This query triggers a reconnect - result = adapter.execute "SELECT COUNT(*) FROM posts;" - assert_equal [[0]], result.rows - assert adapter.active? - end - - test "#execute fails if the connection is closed" do - connection = ::Trilogy.new(@configuration.merge(read_timeout: 1)) - - adapter = trilogy_adapter_with_connection(connection) - adapter.pool = @pool - - assert_raises ActiveRecord::ConnectionFailed do - adapter.transaction do - # Make connection lost for future queries by exceeding the read timeout - assert_raises(ActiveRecord::StatementInvalid) do - adapter.execute "SELECT sleep(2);" - end - assert_not adapter.active? - - adapter.execute "SELECT COUNT(*) FROM posts;" - end - end - - assert_not adapter.active? - - # This query triggers a reconnect - result = adapter.execute "SELECT COUNT(*) FROM posts;" - assert_equal [[0]], result.rows - end - - test "can reconnect after failing to rollback" do - connection = ::Trilogy.new(@configuration.merge(read_timeout: 1)) - - adapter = trilogy_adapter_with_connection(connection) - adapter.pool = @pool - - adapter.transaction do - adapter.execute("SELECT 1") - - # Cause the client to disconnect without the adapter's awareness - assert_raises ::Trilogy::TimeoutError do - adapter.send(:connection).query("SELECT sleep(2)") - end - - raise ActiveRecord::Rollback - end - - result = adapter.execute("SELECT 1") - assert_equal [[1]], result.rows - end - - test "can reconnect after failing to commit" do - connection = Trilogy.new(@configuration.merge(read_timeout: 1)) - - adapter = trilogy_adapter_with_connection(connection) - adapter.pool = @pool - - assert_raises ActiveRecord::ConnectionFailed do - adapter.transaction do - adapter.execute("SELECT 1") - - # Cause the client to disconnect without the adapter's awareness - assert_raises Trilogy::TimeoutError do - adapter.send(:connection).query("SELECT sleep(2)") - end - end - end - - result = adapter.execute("SELECT 1") - assert_equal [[1]], result.rows - end - - test "#execute fails with deadlock error" do - adapter = trilogy_adapter - - new_connection = Trilogy.new(@configuration) - - deadlocking_adapter = trilogy_adapter_with_connection(new_connection) - - # Add seed data - adapter.insert("INSERT INTO posts (title, body) VALUES('Setup', 'Content')") - - adapter.transaction do - adapter.execute( - "UPDATE posts SET title = 'Connection 1' WHERE title != 'Connection 1';" - ) - - # Decrease the lock wait timeout in this session - deadlocking_adapter.execute("SET innodb_lock_wait_timeout = 1") - - assert_raises(ActiveRecord::LockWaitTimeout) do - deadlocking_adapter.execute( - "UPDATE posts SET title = 'Connection 2' WHERE title != 'Connection 2';" - ) - end - end - end - - test "#execute fails with unknown error" do - assert_raises_with_message(ActiveRecord::StatementInvalid, /A random error/) do - connection = Minitest::Mock.new Trilogy.new(@configuration) - connection.expect(:query, nil) { raise Trilogy::ProtocolError, "A random error." } - adapter = trilogy_adapter_with_connection(connection) - - adapter.execute "SELECT * FROM posts;" + @conn.execute "SELECT bogus FROM posts;" end end test "#select_all when query cache is enabled fires the same notification payload for uncached and cached queries" do - @adapter.cache do + @conn.cache do event_fired = false subscription = ->(name, start, finish, id, payload) { event_fired = true @@ -570,7 +166,7 @@ class TrilogyAdapterTest < ActiveRecord::TrilogyTestCase assert_equal "uncached query", payload[:name] assert_includes payload, :connection - assert_equal @adapter, payload[:connection] + assert_equal @conn, payload[:connection] assert_includes payload, :binds assert_equal [], payload[:binds] @@ -585,7 +181,7 @@ class TrilogyAdapterTest < ActiveRecord::TrilogyTestCase assert_not_includes payload, :cached } ActiveSupport::Notifications.subscribed(subscription, "sql.active_record") do - @adapter.select_all "SELECT * FROM posts", "uncached query" + @conn.select_all "SELECT * FROM posts", "uncached query" end assert event_fired @@ -601,7 +197,7 @@ class TrilogyAdapterTest < ActiveRecord::TrilogyTestCase assert_equal "cached query", payload[:name] assert_includes payload, :connection - assert_equal @adapter, payload[:connection] + assert_equal @conn, payload[:connection] assert_includes payload, :binds assert_equal [], payload[:binds] @@ -616,22 +212,21 @@ class TrilogyAdapterTest < ActiveRecord::TrilogyTestCase assert_equal true, payload[:cached] } ActiveSupport::Notifications.subscribed(subscription, "sql.active_record") do - @adapter.select_all "SELECT * FROM posts", "cached query" + @conn.select_all "SELECT * FROM posts", "cached query" end assert event_fired end end test "#execute answers result with valid SQL" do - result = @adapter.execute "SELECT id, author_id, title FROM posts;" + result = @conn.execute "SELECT id, author_id, title FROM posts;" assert_equal %w[id author_id title], result.fields - assert_equal [], result.rows end test "#execute emits a query notification" do assert_notification("sql.active_record") do - @adapter.execute "SELECT * FROM posts;" + @conn.execute "SELECT * FROM posts;" end end @@ -650,7 +245,7 @@ class TrilogyAdapterTest < ActiveRecord::TrilogyTestCase comment: nil }] - indexes = @adapter.indexes("posts").map do |index| + indexes = @conn.indexes("posts").map do |index| { table: index.table, name: index.name, @@ -670,39 +265,39 @@ class TrilogyAdapterTest < ActiveRecord::TrilogyTestCase end test "#indexes answers empty array with no indexes" do - assert_equal [], @adapter.indexes("users") + assert_equal [], @conn.indexes("users") end test "#begin_db_transaction answers empty result" do - result = @adapter.begin_db_transaction + result = @conn.begin_db_transaction assert_equal [], result.rows # rollback transaction so it doesn't bleed into other tests - @adapter.rollback_db_transaction + @conn.rollback_db_transaction end test "#begin_db_transaction raises error" do error = Class.new(Exception) assert_raises error do - @adapter.stub(:raw_execute, -> (*) { raise error }) do - @adapter.begin_db_transaction + @conn.stub(:raw_execute, -> (*) { raise error }) do + @conn.begin_db_transaction end end # rollback transaction so it doesn't bleed into other tests - @adapter.rollback_db_transaction + @conn.rollback_db_transaction end test "#commit_db_transaction answers empty result" do - result = @adapter.commit_db_transaction + result = @conn.commit_db_transaction assert_equal [], result.rows end test "#commit_db_transaction raises error" do error = Class.new(Exception) assert_raises error do - @adapter.stub(:raw_execute, -> (*) { raise error }) do - @adapter.commit_db_transaction + @conn.stub(:raw_execute, -> (*) { raise error }) do + @conn.commit_db_transaction end end end @@ -710,129 +305,21 @@ class TrilogyAdapterTest < ActiveRecord::TrilogyTestCase test "#rollback_db_transaction raises error" do error = Class.new(Exception) assert_raises error do - @adapter.stub(:raw_execute, -> (*) { raise error }) do - @adapter.rollback_db_transaction + @conn.stub(:raw_execute, -> (*) { raise error }) do + @conn.rollback_db_transaction end end end - test "#insert answers ID with ID" do - assert_equal 5, @adapter.insert("INSERT INTO posts (title, body) VALUES ('test', 'content');", "test", nil, 5) - end - - test "#insert answers last ID without ID" do - assert_equal 1, @adapter.insert("INSERT INTO posts (title, body) VALUES ('test', 'content');", "test") - end - - test "#insert answers incremented last ID without ID" do - @adapter.insert("INSERT INTO posts (title, body) VALUES ('test', 'content');", "test") - assert_equal 2, @adapter.insert("INSERT INTO posts (title, body) VALUES ('test', 'content');", "test") - end - - test "#update answers affected row count when updatable" do - @adapter.insert("INSERT INTO posts (title, body) VALUES ('test', 'content');") - assert_equal 1, @adapter.update("UPDATE posts SET title = 'Test' WHERE id = 1;") - end - - test "#update answers zero affected rows when not updatable" do - assert_equal 0, @adapter.update("UPDATE posts SET title = 'Test' WHERE id = 1;") - end - - test "strict mode can be disabled" do - adapter = trilogy_adapter(strict: false) - - adapter.execute "INSERT INTO posts (title) VALUES ('test');" - result = adapter.execute "SELECT * FROM posts;" - assert_equal [[1, nil, "test", "", nil, 0, 0, 0, 0, 0, 0, 0]], result.rows - end - test "#select_value returns a single value" do - assert_equal 123, @adapter.select_value("SELECT 123") - end - - test "#each_hash yields symbolized result rows" do - @adapter.execute "INSERT INTO posts (title, body) VALUES ('test', 'content');" - result = @adapter.execute "SELECT title, body FROM posts;" - - @adapter.each_hash(result) do |row| - assert_equal "test", row[:title] - end - end - - test "#each_hash returns an enumarator of symbolized result rows when no block is given" do - @adapter.execute "INSERT INTO posts (title, body) VALUES ('test', 'content');" - result = @adapter.execute "SELECT * FROM posts;" - rows_enum = @adapter.each_hash result - - assert_equal "test", rows_enum.next[:title] - end - - test "#each_hash returns empty array when results is empty" do - result = @adapter.execute "SELECT * FROM posts;" - rows = @adapter.each_hash result - - assert_empty rows.to_a + assert_equal 123, @conn.select_value("SELECT 123") end test "#error_number answers number for exception" do exception = Minitest::Mock.new exception.expect :error_code, 123 - assert_equal 123, @adapter.error_number(exception) - end - - # We only want to test if QueryLogs functionality is available - if ActiveRecord.respond_to?(:query_transformers) - test "execute uses AbstractAdapter#transform_query when available" do - # Add custom query transformer - old_query_transformers = ActiveRecord.query_transformers - ActiveRecord.query_transformers = [-> (sql, _adapter) { sql + " /* it works */" }] - - sql = "SELECT * FROM posts;" - - mock_connection = Minitest::Mock.new Trilogy.new(@configuration) - adapter = trilogy_adapter_with_connection(mock_connection) - mock_connection.expect :query, nil, [sql + " /* it works */"] - - adapter.execute sql - - assert mock_connection.verify - ensure - # Teardown custom query transformers - ActiveRecord.query_transformers = old_query_transformers - end - end - - test "parses ssl_mode as int" do - adapter = trilogy_adapter(ssl_mode: 0) - adapter.connect! - - assert adapter.active? - end - - test "parses ssl_mode as string" do - adapter = trilogy_adapter(ssl_mode: "disabled") - adapter.connect! - - assert adapter.active? - end - - test "parses ssl_mode as string prefixed" do - adapter = trilogy_adapter(ssl_mode: "SSL_MODE_DISABLED") - adapter.connect! - - assert adapter.active? - end - - def trilogy_adapter_with_connection(connection, **config_overrides) - ActiveRecord::ConnectionAdapters::TrilogyAdapter - .new(connection, nil, {}, @configuration.merge(config_overrides)) - .tap { |conn| conn.execute("SELECT 1") } - end - - def trilogy_adapter(**config_overrides) - ActiveRecord::ConnectionAdapters::TrilogyAdapter - .new(@configuration.merge(config_overrides)) + assert_equal 123, @conn.error_number(exception) end def assert_raises_with_message(exception, message, &block) diff --git a/activerecord/test/cases/json_attribute_test.rb b/activerecord/test/cases/json_attribute_test.rb index afc39d0420e2e..02e0fa1f74a89 100644 --- a/activerecord/test/cases/json_attribute_test.rb +++ b/activerecord/test/cases/json_attribute_test.rb @@ -18,6 +18,7 @@ class JsonDataTypeOnText < ActiveRecord::Base def setup super + @connection.drop_table("json_data_type", if_exists: true) @connection.create_table("json_data_type") do |t| t.string "payload" t.string "settings"