database_cleaner-active_record 1.8.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +5 -0
- data/.travis.yml +15 -3
- data/Appraisals +15 -0
- data/Gemfile +9 -4
- data/README.md +36 -43
- data/bin/setup +0 -1
- data/database_cleaner-active_record.gemspec +11 -20
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/rails_5.1.gemfile +14 -0
- data/gemfiles/rails_5.2.gemfile +14 -0
- data/gemfiles/rails_6.0.gemfile +14 -0
- data/gemfiles/rails_6.1.gemfile +14 -0
- data/lib/database_cleaner/active_record.rb +3 -2
- data/lib/database_cleaner/active_record/base.rb +45 -55
- data/lib/database_cleaner/active_record/deletion.rb +49 -86
- data/lib/database_cleaner/active_record/transaction.rb +10 -47
- data/lib/database_cleaner/active_record/truncation.rb +185 -214
- data/lib/database_cleaner/active_record/version.rb +1 -1
- metadata +31 -39
- data/Gemfile.lock +0 -73
@@ -1,107 +1,70 @@
|
|
1
1
|
require 'active_record'
|
2
|
-
require 'active_record/connection_adapters/abstract_adapter'
|
3
|
-
require "database_cleaner/generic/truncation"
|
4
2
|
require 'database_cleaner/active_record/truncation'
|
5
3
|
|
6
4
|
module DatabaseCleaner
|
7
|
-
module
|
8
|
-
|
9
|
-
def
|
10
|
-
|
5
|
+
module ActiveRecord
|
6
|
+
class Deletion < Truncation
|
7
|
+
def clean
|
8
|
+
connection.disable_referential_integrity do
|
9
|
+
if pre_count? && connection.respond_to?(:pre_count_tables)
|
10
|
+
delete_tables(connection, connection.pre_count_tables(tables_to_truncate(connection)))
|
11
|
+
else
|
12
|
+
delete_tables(connection, tables_to_truncate(connection))
|
13
|
+
end
|
14
|
+
end
|
11
15
|
end
|
12
|
-
end
|
13
16
|
|
14
|
-
|
15
|
-
def delete_table(table_name)
|
16
|
-
execute("DELETE FROM #{quote_table_name(table_name)};")
|
17
|
-
end
|
18
|
-
end
|
17
|
+
private
|
19
18
|
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
def delete_tables(connection, table_names)
|
20
|
+
table_names.each do |table_name|
|
21
|
+
delete_table(connection, table_name)
|
22
|
+
end
|
23
23
|
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
module ActiveRecord
|
29
|
-
module ConnectionAdapters
|
30
|
-
AbstractAdapter.class_eval { include DatabaseCleaner::ConnectionAdapters::AbstractDeleteAdapter }
|
31
|
-
|
32
|
-
JdbcAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(JdbcAdapter)
|
33
|
-
AbstractMysqlAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(AbstractMysqlAdapter)
|
34
|
-
Mysql2Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(Mysql2Adapter)
|
35
|
-
SQLiteAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(SQLiteAdapter)
|
36
|
-
SQLite3Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(SQLite3Adapter)
|
37
|
-
PostgreSQLAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(PostgreSQLAdapter)
|
38
|
-
IBM_DBAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(IBM_DBAdapter)
|
39
|
-
SQLServerAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(SQLServerAdapter)
|
40
|
-
OracleEnhancedAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::OracleDeleteAdapter } if defined?(OracleEnhancedAdapter)
|
41
|
-
end
|
42
|
-
end
|
43
24
|
|
44
|
-
|
45
|
-
|
46
|
-
def tables_to_truncate(connection)
|
47
|
-
if information_schema_exists?(connection)
|
48
|
-
(@only || tables_with_new_rows(connection)) - @tables_to_exclude
|
49
|
-
else
|
50
|
-
super
|
25
|
+
def delete_table connection, table_name
|
26
|
+
connection.execute("DELETE FROM #{connection.quote_table_name(table_name)}")
|
51
27
|
end
|
52
|
-
end
|
53
28
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
29
|
+
def tables_to_truncate(connection)
|
30
|
+
if information_schema_exists?(connection)
|
31
|
+
@except += connection.database_cleaner_view_cache + migration_storage_names
|
32
|
+
(@only.any? ? @only : tables_with_new_rows(connection)) - @except
|
33
|
+
else
|
34
|
+
super
|
35
|
+
end
|
60
36
|
end
|
61
|
-
end
|
62
37
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
38
|
+
def tables_with_new_rows(connection)
|
39
|
+
stats = table_stats_query(connection)
|
40
|
+
if stats != ''
|
41
|
+
connection.select_values(stats)
|
42
|
+
else
|
43
|
+
[]
|
44
|
+
end
|
45
|
+
end
|
68
46
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
WHERE table_schema = database()
|
74
|
-
AND #{::DatabaseCleaner::ActiveRecord::Base.exclusion_condition('table_name')};
|
75
|
-
SQL
|
76
|
-
queries = tables.map do |table|
|
77
|
-
"(SELECT #{connection.quote(table)} FROM #{connection.quote_table_name(table)} LIMIT 1)"
|
47
|
+
def table_stats_query(connection)
|
48
|
+
@table_stats_query ||= build_table_stats_query(connection)
|
49
|
+
ensure
|
50
|
+
@table_stats_query = nil unless @cache_tables
|
78
51
|
end
|
79
|
-
queries.join(' UNION ALL ')
|
80
|
-
end
|
81
52
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
53
|
+
def build_table_stats_query(connection)
|
54
|
+
tables = connection.select_values(<<-SQL)
|
55
|
+
SELECT table_name
|
56
|
+
FROM information_schema.tables
|
57
|
+
WHERE table_schema = database()
|
58
|
+
AND #{self.class.exclusion_condition('table_name')};
|
59
|
+
SQL
|
60
|
+
queries = tables.map do |table|
|
61
|
+
"(SELECT #{connection.quote(table)} FROM #{connection.quote_table_name(table)} LIMIT 1)"
|
90
62
|
end
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
class Deletion < Truncation
|
95
|
-
if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
|
96
|
-
include SelectiveTruncation
|
97
|
-
end
|
63
|
+
queries.join(' UNION ALL ')
|
64
|
+
end
|
98
65
|
|
99
|
-
|
100
|
-
|
101
|
-
connection.disable_referential_integrity do
|
102
|
-
tables_to_truncate(connection).each do |table_name|
|
103
|
-
connection.delete_table table_name
|
104
|
-
end
|
66
|
+
def information_schema_exists? connection
|
67
|
+
connection.adapter_name == "Mysql2"
|
105
68
|
end
|
106
69
|
end
|
107
70
|
end
|
@@ -1,59 +1,22 @@
|
|
1
1
|
require 'database_cleaner/active_record/base'
|
2
|
-
require 'database_cleaner/generic/transaction'
|
3
2
|
|
4
|
-
module DatabaseCleaner
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
module DatabaseCleaner
|
4
|
+
module ActiveRecord
|
5
|
+
class Transaction < Base
|
6
|
+
def start
|
7
|
+
# Hack to make sure that the connection is properly set up before cleaning
|
8
|
+
connection_class.connection.transaction {}
|
8
9
|
|
9
|
-
|
10
|
-
# Hack to make sure that the connection is properly setup for
|
11
|
-
# the clean code.
|
12
|
-
connection_class.connection.transaction{ }
|
13
|
-
|
14
|
-
if connection_maintains_transaction_count?
|
15
|
-
if connection_class.connection.respond_to?(:increment_open_transactions)
|
16
|
-
connection_class.connection.increment_open_transactions
|
17
|
-
else
|
18
|
-
connection_class.__send__(:increment_open_transactions)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
if connection_class.connection.respond_to?(:begin_transaction)
|
22
|
-
connection_class.connection.begin_transaction :joinable => false
|
23
|
-
else
|
24
|
-
connection_class.connection.begin_db_transaction
|
10
|
+
connection_class.connection.begin_transaction joinable: false
|
25
11
|
end
|
26
|
-
end
|
27
|
-
|
28
12
|
|
29
|
-
def clean
|
30
|
-
connection_class.connection_pool.connections.each do |connection|
|
31
|
-
next unless connection.open_transactions > 0
|
32
13
|
|
33
|
-
|
14
|
+
def clean
|
15
|
+
connection_class.connection_pool.connections.each do |connection|
|
16
|
+
next unless connection.open_transactions > 0
|
34
17
|
connection.rollback_transaction
|
35
|
-
else
|
36
|
-
connection.rollback_db_transaction
|
37
|
-
end
|
38
|
-
|
39
|
-
# The below is for handling after_commit hooks.. see https://github.com/bmabey/database_cleaner/issues/99
|
40
|
-
if connection.respond_to?(:rollback_transaction_records, true)
|
41
|
-
connection.send(:rollback_transaction_records, true)
|
42
|
-
end
|
43
|
-
|
44
|
-
if connection_maintains_transaction_count?
|
45
|
-
if connection.respond_to?(:decrement_open_transactions)
|
46
|
-
connection.decrement_open_transactions
|
47
|
-
else
|
48
|
-
connection_class.__send__(:decrement_open_transactions)
|
49
|
-
end
|
50
18
|
end
|
51
19
|
end
|
52
20
|
end
|
53
|
-
|
54
|
-
def connection_maintains_transaction_count?
|
55
|
-
ActiveRecord::VERSION::MAJOR < 4
|
56
|
-
end
|
57
|
-
|
58
21
|
end
|
59
22
|
end
|
@@ -1,278 +1,249 @@
|
|
1
|
+
require "delegate"
|
1
2
|
require 'active_record/base'
|
2
3
|
require 'database_cleaner/active_record/base'
|
3
|
-
require 'active_record/connection_adapters/abstract_adapter'
|
4
|
-
|
5
|
-
#Load available connection adapters
|
6
|
-
%w(
|
7
|
-
abstract_mysql_adapter postgresql_adapter sqlite3_adapter mysql_adapter mysql2_adapter oracle_enhanced_adapter
|
8
|
-
).each do |known_adapter|
|
9
|
-
begin
|
10
|
-
require "active_record/connection_adapters/#{known_adapter}"
|
11
|
-
rescue LoadError
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
require "database_cleaner/generic/truncation"
|
16
|
-
require 'database_cleaner/active_record/base'
|
17
4
|
|
18
5
|
module DatabaseCleaner
|
19
|
-
module
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
def database_cleaner_view_cache
|
25
|
-
@views ||= select_values("select table_name from information_schema.views where table_schema = '#{current_database}'") rescue []
|
26
|
-
end
|
27
|
-
|
28
|
-
def database_cleaner_table_cache
|
29
|
-
# the adapters don't do caching (#130) but we make the assumption that the list stays the same in tests
|
30
|
-
@database_cleaner_tables ||= database_tables
|
31
|
-
end
|
32
|
-
|
33
|
-
def database_tables
|
34
|
-
::ActiveRecord::VERSION::MAJOR >= 5 ? data_sources : tables
|
35
|
-
end
|
36
|
-
|
37
|
-
def truncate_table(table_name)
|
38
|
-
raise NotImplementedError
|
39
|
-
end
|
40
|
-
|
41
|
-
def truncate_tables(tables)
|
42
|
-
tables.each do |table_name|
|
43
|
-
self.truncate_table(table_name)
|
6
|
+
module ActiveRecord
|
7
|
+
class Truncation < Base
|
8
|
+
def initialize(opts={})
|
9
|
+
if !opts.empty? && !(opts.keys - [:only, :except, :pre_count, :cache_tables]).empty?
|
10
|
+
raise ArgumentError, "The only valid options are :only, :except, :pre_count, and :cache_tables. You specified #{opts.keys.join(',')}."
|
44
11
|
end
|
45
|
-
end
|
46
|
-
end
|
47
12
|
|
48
|
-
|
49
|
-
|
50
|
-
execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
|
51
|
-
end
|
13
|
+
@only = Array(opts[:only]).dup
|
14
|
+
@except = Array(opts[:except]).dup
|
52
15
|
|
53
|
-
|
54
|
-
|
16
|
+
@pre_count = opts[:pre_count]
|
17
|
+
@cache_tables = opts.has_key?(:cache_tables) ? !!opts[:cache_tables] : true
|
55
18
|
end
|
56
19
|
|
57
|
-
def
|
58
|
-
|
59
|
-
|
20
|
+
def clean
|
21
|
+
connection.disable_referential_integrity do
|
22
|
+
if pre_count? && connection.respond_to?(:pre_count_truncate_tables)
|
23
|
+
connection.pre_count_truncate_tables(tables_to_truncate(connection))
|
24
|
+
else
|
25
|
+
connection.truncate_tables(tables_to_truncate(connection))
|
26
|
+
end
|
27
|
+
end
|
60
28
|
end
|
61
29
|
|
62
30
|
private
|
63
31
|
|
64
|
-
def
|
65
|
-
|
66
|
-
# select_value("SELECT 1") #=> "1"
|
67
|
-
select_value("SELECT EXISTS (SELECT 1 FROM #{quote_table_name(table)} LIMIT 1)").to_i
|
32
|
+
def connection
|
33
|
+
@connection ||= ConnectionWrapper.new(connection_class.connection)
|
68
34
|
end
|
69
35
|
|
70
|
-
def
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
36
|
+
def tables_to_truncate(connection)
|
37
|
+
if @only.none?
|
38
|
+
all_tables = cache_tables? ? connection.database_cleaner_table_cache : connection.database_tables
|
39
|
+
@only = all_tables.map { |table| table.split(".").last }
|
40
|
+
end
|
41
|
+
@except += connection.database_cleaner_view_cache + migration_storage_names
|
42
|
+
@only - @except
|
77
43
|
end
|
78
44
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
has_rows?(table) || auto_increment_value(table) > 1
|
45
|
+
def migration_storage_names
|
46
|
+
[
|
47
|
+
DatabaseCleaner::ActiveRecord::Base.migration_table_name,
|
48
|
+
::ActiveRecord::Base.internal_metadata_table_name,
|
49
|
+
]
|
85
50
|
end
|
86
51
|
|
87
|
-
def
|
88
|
-
|
52
|
+
def cache_tables?
|
53
|
+
!!@cache_tables
|
89
54
|
end
|
90
|
-
end
|
91
55
|
|
92
|
-
|
93
|
-
|
94
|
-
execute("TRUNCATE #{quote_table_name(table_name)} IMMEDIATE")
|
56
|
+
def pre_count?
|
57
|
+
@pre_count == true
|
95
58
|
end
|
96
59
|
end
|
97
60
|
|
98
|
-
|
99
|
-
def
|
100
|
-
|
101
|
-
|
102
|
-
|
61
|
+
class ConnectionWrapper < SimpleDelegator
|
62
|
+
def initialize(connection)
|
63
|
+
extend AbstractAdapter
|
64
|
+
case connection.adapter_name
|
65
|
+
when "Mysql2"
|
66
|
+
extend AbstractMysqlAdapter
|
67
|
+
when "SQLite"
|
68
|
+
extend AbstractMysqlAdapter
|
69
|
+
extend SQLiteAdapter
|
70
|
+
when "PostgreSQL"
|
71
|
+
extend AbstractMysqlAdapter
|
72
|
+
extend PostgreSQLAdapter
|
103
73
|
end
|
74
|
+
super(connection)
|
104
75
|
end
|
105
|
-
alias truncate_table delete_table
|
106
76
|
|
107
|
-
|
108
|
-
|
109
|
-
|
77
|
+
module AbstractAdapter
|
78
|
+
# used to be called views but that can clash with gems like schema_plus
|
79
|
+
# this gem is not meant to be exposing such an extra interface any way
|
80
|
+
def database_cleaner_view_cache
|
81
|
+
@views ||= select_values("select table_name from information_schema.views where table_schema = '#{current_database}'") rescue []
|
82
|
+
end
|
110
83
|
|
111
|
-
|
84
|
+
def database_cleaner_table_cache
|
85
|
+
# the adapters don't do caching (#130) but we make the assumption that the list stays the same in tests
|
86
|
+
@database_cleaner_tables ||= database_tables
|
87
|
+
end
|
112
88
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
end
|
117
|
-
end
|
89
|
+
def database_tables
|
90
|
+
tables
|
91
|
+
end
|
118
92
|
|
119
|
-
|
120
|
-
|
121
|
-
begin
|
122
|
-
execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
|
93
|
+
def truncate_table(table_name)
|
94
|
+
execute("TRUNCATE TABLE #{quote_table_name(table_name)}")
|
123
95
|
rescue ::ActiveRecord::StatementInvalid
|
124
|
-
execute("DELETE FROM #{quote_table_name(table_name)}
|
96
|
+
execute("DELETE FROM #{quote_table_name(table_name)}")
|
125
97
|
end
|
126
|
-
end
|
127
|
-
end
|
128
98
|
|
129
|
-
|
130
|
-
|
131
|
-
|
99
|
+
def truncate_tables(tables)
|
100
|
+
tables.each { |t| truncate_table(t) }
|
101
|
+
end
|
132
102
|
end
|
133
|
-
end
|
134
103
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
104
|
+
module AbstractMysqlAdapter
|
105
|
+
def pre_count_truncate_tables(tables)
|
106
|
+
truncate_tables(pre_count_tables(tables))
|
107
|
+
end
|
139
108
|
|
140
|
-
|
141
|
-
|
142
|
-
|
109
|
+
def pre_count_tables(tables)
|
110
|
+
tables.select { |table| has_been_used?(table) }
|
111
|
+
end
|
143
112
|
|
144
|
-
|
145
|
-
@restart_identity ||= db_version >= 80400 ? 'RESTART IDENTITY' : ''
|
146
|
-
end
|
113
|
+
private
|
147
114
|
|
148
|
-
|
149
|
-
|
150
|
-
|
115
|
+
def row_count(table)
|
116
|
+
select_value("SELECT EXISTS (SELECT 1 FROM #{quote_table_name(table)} LIMIT 1)")
|
117
|
+
end
|
151
118
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
119
|
+
def auto_increment_value(table)
|
120
|
+
select_value(<<-SQL).to_i
|
121
|
+
SELECT auto_increment
|
122
|
+
FROM information_schema.tables
|
123
|
+
WHERE table_name = '#{table}'
|
124
|
+
AND table_schema = database()
|
125
|
+
SQL
|
126
|
+
end
|
156
127
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
128
|
+
# This method tells us if the given table has been inserted into since its
|
129
|
+
# last truncation. Note that the table might have been populated, which
|
130
|
+
# increased the auto-increment counter, but then cleaned again such that
|
131
|
+
# it appears empty now.
|
132
|
+
def has_been_used?(table)
|
133
|
+
has_rows?(table) || auto_increment_value(table) > 1
|
134
|
+
end
|
161
135
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
# with using the base list. If a table exists in multiple schemas
|
166
|
-
# within the search path, truncation without the schema name could
|
167
|
-
# result in confusing, if not unexpected results.
|
168
|
-
@database_cleaner_tables ||= tables_with_schema
|
136
|
+
def has_rows?(table)
|
137
|
+
row_count(table) > 0
|
138
|
+
end
|
169
139
|
end
|
170
140
|
|
171
|
-
|
141
|
+
module SQLiteAdapter
|
142
|
+
def truncate_table(table_name)
|
143
|
+
super
|
144
|
+
if uses_sequence?
|
145
|
+
execute("DELETE FROM sqlite_sequence where name = '#{table_name}';")
|
146
|
+
end
|
147
|
+
end
|
172
148
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
# was ever inserted into.
|
177
|
-
def has_been_used?(table)
|
178
|
-
return has_rows?(table) unless has_sequence?(table)
|
149
|
+
def truncate_tables(tables)
|
150
|
+
tables.each { |t| truncate_table(t) }
|
151
|
+
end
|
179
152
|
|
180
|
-
|
181
|
-
|
182
|
-
|
153
|
+
def pre_count_truncate_tables(tables)
|
154
|
+
truncate_tables(pre_count_tables(tables))
|
155
|
+
end
|
183
156
|
|
184
|
-
|
185
|
-
|
186
|
-
|
157
|
+
def pre_count_tables(tables)
|
158
|
+
sequences = fetch_sequences
|
159
|
+
tables.select { |table| has_been_used?(table, sequences) }
|
160
|
+
end
|
187
161
|
|
188
|
-
|
189
|
-
select_value("SELECT true FROM #{table} LIMIT 1;")
|
190
|
-
end
|
162
|
+
private
|
191
163
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
end
|
203
|
-
end
|
204
|
-
end
|
205
|
-
end
|
164
|
+
def fetch_sequences
|
165
|
+
return {} unless uses_sequence?
|
166
|
+
results = select_all("SELECT * FROM sqlite_sequence")
|
167
|
+
Hash[results.rows]
|
168
|
+
end
|
169
|
+
|
170
|
+
def has_been_used?(table, sequences)
|
171
|
+
count = sequences.fetch(table) { row_count(table) }
|
172
|
+
count > 0
|
173
|
+
end
|
206
174
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
AbstractAdapter.class_eval { include DatabaseCleaner::ConnectionAdapters::AbstractAdapter }
|
175
|
+
def row_count(table)
|
176
|
+
select_value("SELECT EXISTS (SELECT 1 FROM #{quote_table_name(table)} LIMIT 1)")
|
177
|
+
end
|
211
178
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
JdbcAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::TruncateOrDelete }
|
179
|
+
# Returns a boolean indicating if the SQLite database is using the sqlite_sequence table.
|
180
|
+
def uses_sequence?
|
181
|
+
select_value("SELECT name FROM sqlite_master WHERE type='table' AND name='sqlite_sequence';")
|
182
|
+
end
|
217
183
|
end
|
218
|
-
end
|
219
|
-
AbstractMysqlAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::AbstractMysqlAdapter } if defined?(AbstractMysqlAdapter)
|
220
|
-
Mysql2Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::AbstractMysqlAdapter } if defined?(Mysql2Adapter)
|
221
|
-
MysqlAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::AbstractMysqlAdapter } if defined?(MysqlAdapter)
|
222
|
-
SQLiteAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::SQLiteAdapter } if defined?(SQLiteAdapter)
|
223
|
-
SQLite3Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::SQLiteAdapter } if defined?(SQLite3Adapter)
|
224
|
-
PostgreSQLAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::PostgreSQLAdapter } if defined?(PostgreSQLAdapter)
|
225
|
-
IBM_DBAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::IBM_DBAdapter } if defined?(IBM_DBAdapter)
|
226
|
-
SQLServerAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::TruncateOrDelete } if defined?(SQLServerAdapter)
|
227
|
-
OracleEnhancedAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::OracleAdapter } if defined?(OracleEnhancedAdapter)
|
228
|
-
end
|
229
|
-
end
|
230
184
|
|
231
|
-
module
|
232
|
-
|
233
|
-
|
234
|
-
|
185
|
+
module PostgreSQLAdapter
|
186
|
+
def database_tables
|
187
|
+
tables_with_schema
|
188
|
+
end
|
235
189
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
if pre_count? && connection.respond_to?(:pre_count_truncate_tables)
|
240
|
-
connection.pre_count_truncate_tables(tables_to_truncate(connection), {:reset_ids => reset_ids?})
|
241
|
-
else
|
242
|
-
connection.truncate_tables(tables_to_truncate(connection))
|
190
|
+
def truncate_tables(table_names)
|
191
|
+
return if table_names.nil? || table_names.empty?
|
192
|
+
execute("TRUNCATE TABLE #{table_names.map{|name| quote_table_name(name)}.join(', ')} RESTART IDENTITY CASCADE;")
|
243
193
|
end
|
244
|
-
end
|
245
|
-
end
|
246
194
|
|
247
|
-
|
195
|
+
def pre_count_truncate_tables(tables)
|
196
|
+
truncate_tables(pre_count_tables(tables))
|
197
|
+
end
|
248
198
|
|
249
|
-
|
250
|
-
|
251
|
-
to_reject = (@tables_to_exclude + connection.database_cleaner_view_cache)
|
252
|
-
(@only || tables_in_db).reject do |table|
|
253
|
-
if ( m = table.match(/([^.]+)$/) )
|
254
|
-
to_reject.include?(m[1])
|
255
|
-
else
|
256
|
-
false
|
199
|
+
def pre_count_tables(tables)
|
200
|
+
tables.select { |table| has_been_used?(table) }
|
257
201
|
end
|
258
|
-
end
|
259
|
-
end
|
260
202
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
203
|
+
def database_cleaner_table_cache
|
204
|
+
# AR returns a list of tables without schema but then returns a
|
205
|
+
# migrations table with the schema. There are other problems, too,
|
206
|
+
# with using the base list. If a table exists in multiple schemas
|
207
|
+
# within the search path, truncation without the schema name could
|
208
|
+
# result in confusing, if not unexpected results.
|
209
|
+
@database_cleaner_tables ||= tables_with_schema
|
210
|
+
end
|
265
211
|
|
266
|
-
|
267
|
-
!!@cache_tables
|
268
|
-
end
|
212
|
+
private
|
269
213
|
|
270
|
-
|
271
|
-
|
272
|
-
|
214
|
+
# Returns a boolean indicating if the given table has an auto-inc number higher than 0.
|
215
|
+
# Note, this is different than an empty table since an table may populated, the index increased,
|
216
|
+
# but then the table is cleaned. In other words, this function tells us if the given table
|
217
|
+
# was ever inserted into.
|
218
|
+
def has_been_used?(table)
|
219
|
+
return has_rows?(table) unless has_sequence?(table)
|
220
|
+
|
221
|
+
cur_val = select_value("SELECT currval('#{table}_id_seq');").to_i rescue 0
|
222
|
+
cur_val > 0
|
223
|
+
end
|
224
|
+
|
225
|
+
def has_sequence?(table)
|
226
|
+
select_value("SELECT true FROM pg_class WHERE relname = '#{table}_id_seq';")
|
227
|
+
end
|
273
228
|
|
274
|
-
|
275
|
-
|
229
|
+
def has_rows?(table)
|
230
|
+
select_value("SELECT true FROM #{table} LIMIT 1;")
|
231
|
+
end
|
232
|
+
|
233
|
+
def tables_with_schema
|
234
|
+
rows = select_rows <<-_SQL
|
235
|
+
SELECT schemaname || '.' || tablename
|
236
|
+
FROM pg_tables
|
237
|
+
WHERE
|
238
|
+
tablename !~ '_prt_' AND
|
239
|
+
#{DatabaseCleaner::ActiveRecord::Base.exclusion_condition('tablename')} AND
|
240
|
+
schemaname = ANY (current_schemas(false))
|
241
|
+
_SQL
|
242
|
+
rows.collect { |result| result.first }
|
243
|
+
end
|
244
|
+
end
|
276
245
|
end
|
246
|
+
private_constant :ConnectionWrapper
|
277
247
|
end
|
278
248
|
end
|
249
|
+
|