Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions lib/database_cleaner/active_record/truncation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -226,20 +226,27 @@ def database_cleaner_table_cache
# but then the table is cleaned. In other words, this function tells us if the given table
# was ever inserted into.
def has_been_used?(table)
return has_rows?(table) unless has_sequence?(table)
sequence = serial_sequence(table)
return has_rows?(table) unless sequence

cur_val = select_value("SELECT last_value from #{table}_id_seq;").to_i
cur_val = select_value("SELECT last_value from #{sequence};").to_i
cur_val > 0
end

def has_sequence?(table)
select_value("SELECT true FROM pg_class WHERE relname = '#{table}_id_seq';")
end

def has_rows?(table)
select_value("SELECT true FROM #{table} LIMIT 1;")
end

# Looks up the real sequence name instead of guessing "#{table}_id_seq", which can be
# wrong once Postgres truncates it to fit its 63-byte identifier limit.
def serial_sequence(table)
select_value("SELECT pg_get_serial_sequence(#{quote(table)}, 'id')")
rescue ::ActiveRecord::StatementInvalid => e
raise unless e.cause.is_a?(PG::UndefinedColumn) # no "id" column at all

nil
end

def tables_with_schema
rows = select_rows <<-_SQL
SELECT schemaname || '.' || tablename
Expand Down
30 changes: 30 additions & 0 deletions spec/database_cleaner/active_record/truncation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,36 @@
expect(strategy.send(:connection)).to receive(:truncate_tables).with(['users'], {:truncate_option=>:restrict})
strategy.clean
end

if helper.db == :postgres
context "with a table name at Postgres' identifier length limit" do
# :only this table: the rest of this file leaves "users"/"user_profiles" in whatever
# state earlier (mocked) examples left them in, and "user_profiles" has a real FK
# onto "users" that RESTRICT would trip on unrelated to what's under test here.
subject(:strategy) { described_class.new(pre_count: true, only: [long_table_name]) }
let(:long_table_name) { "u" * 58 + "_long" } # 63 chars: Postgres' NAMEDATALEN limit

before do
connection.execute("CREATE TABLE #{long_table_name} (id SERIAL PRIMARY KEY, name INTEGER);")
end

after do
connection.execute("DROP TABLE IF EXISTS #{long_table_name};")
end

it "does not raise when checking whether the table has been used" do
expect { strategy.clean }.not_to raise_error
end

it "detects activity on the table via its actual (differently-truncated) sequence" do
connection.execute("INSERT INTO #{long_table_name} (name) VALUES (1);")
connection.execute("INSERT INTO #{long_table_name} (name) VALUES (2);")

expect(strategy.send(:connection)).to receive(:truncate_tables).with([long_table_name], {truncate_option: :restrict})
strategy.clean
end
end
end
end

describe "with truncate_option set to cascade" do
Expand Down