From 2c9a5b79437f916c2a6ef24032702e15ef0bcb1c Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Wed, 8 Mar 2017 17:01:17 +0900 Subject: [PATCH 1/4] Expose delete_multiple as a public method on AR connection adapter --- lib/database_rewinder/cleaner.rb | 2 +- lib/database_rewinder/multiple_statements_executor.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/database_rewinder/cleaner.rb b/lib/database_rewinder/cleaner.rb index 67bcec6..0569ef8 100644 --- a/lib/database_rewinder/cleaner.rb +++ b/lib/database_rewinder/cleaner.rb @@ -55,7 +55,7 @@ def delete_all(ar_conn, tables, multiple: true) # Print the warning message, then fall back to non-multiple deletion Kernel.warn "WARNING: You may be executing DatabaseRewinder inside a transactional test. You're presumably misconfiguring your tests. Please read DatabaseRewinder's document, and properly configure your tests." else - ar_conn.execute_multiple tables.map {|t| "DELETE FROM #{ar_conn.quote_table_name(t)}"}.join(';') + ar_conn.delete_multiple tables return end end diff --git a/lib/database_rewinder/multiple_statements_executor.rb b/lib/database_rewinder/multiple_statements_executor.rb index 134170d..ecf6295 100644 --- a/lib/database_rewinder/multiple_statements_executor.rb +++ b/lib/database_rewinder/multiple_statements_executor.rb @@ -7,6 +7,10 @@ def supports_multiple_statements? %w(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter ActiveRecord::ConnectionAdapters::Mysql2Adapter ActiveRecord::ConnectionAdapters::SQLite3Adapter).include? self.class.name end + def delete_multiple(tables) + execute_multiple tables.map {|t| "DELETE FROM #{quote_table_name(t)}"}.join(';') + end + def execute_multiple(sql) #TODO Use ADAPTER_NAME when we've dropped AR 4.1 support case self.class.name From d02401c172a9f612344bc7e8595e436147286245 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Wed, 8 Mar 2017 17:14:03 +0900 Subject: [PATCH 2/4] Only accept delete_multiple from outside --- .../multiple_statements_executor.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/database_rewinder/multiple_statements_executor.rb b/lib/database_rewinder/multiple_statements_executor.rb index ecf6295..472a200 100644 --- a/lib/database_rewinder/multiple_statements_executor.rb +++ b/lib/database_rewinder/multiple_statements_executor.rb @@ -8,17 +8,15 @@ def supports_multiple_statements? end def delete_multiple(tables) - execute_multiple tables.map {|t| "DELETE FROM #{quote_table_name(t)}"}.join(';') - end - - def execute_multiple(sql) #TODO Use ADAPTER_NAME when we've dropped AR 4.1 support case self.class.name when 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter' + sql = tables_to_one_delete_sql tables disable_referential_integrity { log(sql) { @connection.exec sql } } when 'ActiveRecord::ConnectionAdapters::Mysql2Adapter' if @connection.query_options[:connect_flags] & Mysql2::Client::MULTI_STATEMENTS != 0 disable_referential_integrity do + sql = tables_to_one_delete_sql tables _result = log(sql) { @connection.query sql } while @connection.next_result # just to make sure that all queries are finished @@ -33,6 +31,7 @@ def execute_multiple(sql) begin # disable_referential_integrity client.query("SET FOREIGN_KEY_CHECKS = 0") + sql = tables_to_one_delete_sql tables _result = log(sql) { client.query sql } while client.next_result # just to make sure that all queries are finished @@ -43,11 +42,17 @@ def execute_multiple(sql) end end when 'ActiveRecord::ConnectionAdapters::SQLite3Adapter' + sql = tables_to_one_delete_sql tables disable_referential_integrity { log(sql) { @connection.execute_batch sql } } else raise 'Multiple deletion is not supported with the current database adapter.' end end + + private + def tables_to_one_delete_sql(tables) + tables.map {|t| "DELETE FROM #{quote_table_name(t)}"}.join(';') + end end end end From 4721ccbcb291d66b62b06c6f4f27b04fd6ccf1d0 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Wed, 8 Mar 2017 17:29:37 +0900 Subject: [PATCH 3/4] Readability --- lib/database_rewinder/multiple_statements_executor.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/database_rewinder/multiple_statements_executor.rb b/lib/database_rewinder/multiple_statements_executor.rb index 472a200..9d6b0dc 100644 --- a/lib/database_rewinder/multiple_statements_executor.rb +++ b/lib/database_rewinder/multiple_statements_executor.rb @@ -13,6 +13,7 @@ def delete_multiple(tables) when 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter' sql = tables_to_one_delete_sql tables disable_referential_integrity { log(sql) { @connection.exec sql } } + when 'ActiveRecord::ConnectionAdapters::Mysql2Adapter' if @connection.query_options[:connect_flags] & Mysql2::Client::MULTI_STATEMENTS != 0 disable_referential_integrity do @@ -23,6 +24,7 @@ def delete_multiple(tables) _result = @connection.store_result end end + else query_options = @connection.query_options.dup query_options[:connect_flags] |= Mysql2::Client::MULTI_STATEMENTS @@ -41,9 +43,11 @@ def delete_multiple(tables) client.close end end + when 'ActiveRecord::ConnectionAdapters::SQLite3Adapter' sql = tables_to_one_delete_sql tables disable_referential_integrity { log(sql) { @connection.execute_batch sql } } + else raise 'Multiple deletion is not supported with the current database adapter.' end From 6e2cdd297d99cc093860eb1a4ac0591ab0d368c8 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Mon, 13 Mar 2017 19:00:08 +0900 Subject: [PATCH 4/4] WIP use stored procedure for MySQL multiple deletion --- .../multiple_statements_executor.rb | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/lib/database_rewinder/multiple_statements_executor.rb b/lib/database_rewinder/multiple_statements_executor.rb index 9d6b0dc..51edd32 100644 --- a/lib/database_rewinder/multiple_statements_executor.rb +++ b/lib/database_rewinder/multiple_statements_executor.rb @@ -26,21 +26,12 @@ def delete_multiple(tables) end else - query_options = @connection.query_options.dup - query_options[:connect_flags] |= Mysql2::Client::MULTI_STATEMENTS - # opens another connection to the DB - client = Mysql2::Client.new query_options - begin - # disable_referential_integrity - client.query("SET FOREIGN_KEY_CHECKS = 0") - sql = tables_to_one_delete_sql tables - _result = log(sql) { client.query sql } - while client.next_result - # just to make sure that all queries are finished - _result = client.store_result - end - ensure - client.close + @connection.query 'drop procedure if exists rewind_em_all' + @connection.query <<-SQL +create procedure rewind_em_all(in tables text, in num integer) begin declare i int default 0; while i < num do set i = i + 1; set @delete_sql = concat('DELETE FROM ', substring_index(substring_index(tables, ',', i), ',', -1)); prepare stmt from @delete_sql; execute stmt; deallocate prepare stmt; end while; end; +SQL + disable_referential_integrity do + @connection.query "call rewind_em_all('#{tables.join(',')}', #{tables.length})" end end