diff --git a/lib/outboxer/database.rb b/lib/outboxer/database.rb index 5041dcbb..aab22bd5 100644 --- a/lib/outboxer/database.rb +++ b/lib/outboxer/database.rb @@ -67,5 +67,48 @@ def disconnect(logger: nil) ActiveRecord::Base.connection_handler.connection_pool_list.each(&:disconnect!) logger&.info "Outboxer disconnected from database" end + + # Truncates all Outboxer tables. + # + # - Runs inside a transaction for atomicity where supported. + # - Works across PostgreSQL and MySQL without adapter branching. + # - Automatically resets auto-increment / identity sequences. + # + # @param logger [#info, #warn, #error, nil] optional logger + # @return [void] + def truncate(logger: nil) + logger&.warn("Outboxer truncating tables...") + + ActiveRecord::Base.connection_pool.with_connection do |connection| + if connection.adapter_name.downcase.include?("postgres") + connection.execute(<<~SQL) + TRUNCATE TABLE + outboxer_message_counts, + outboxer_message_totals, + outboxer_frames, + outboxer_exceptions, + outboxer_messages, + outboxer_signals, + outboxer_publishers + RESTART IDENTITY; + SQL + else + foreign_key_checks = connection.select_value("SELECT @@FOREIGN_KEY_CHECKS;").to_i + + begin + connection.execute("SET FOREIGN_KEY_CHECKS = 0;") + connection.execute("TRUNCATE TABLE outboxer_message_counts;") + connection.execute("TRUNCATE TABLE outboxer_message_totals;") + connection.execute("TRUNCATE TABLE outboxer_frames;") + connection.execute("TRUNCATE TABLE outboxer_exceptions;") + connection.execute("TRUNCATE TABLE outboxer_messages;") + connection.execute("TRUNCATE TABLE outboxer_signals;") + connection.execute("TRUNCATE TABLE outboxer_publishers;") + ensure + connection.execute("SET FOREIGN_KEY_CHECKS = #{foreign_key_checks};") + end + end + end + end end end diff --git a/spec/lib/outboxer/database/truncate_spec.rb b/spec/lib/outboxer/database/truncate_spec.rb new file mode 100644 index 00000000..e676c049 --- /dev/null +++ b/spec/lib/outboxer/database/truncate_spec.rb @@ -0,0 +1,49 @@ +require "rails_helper" + +module Outboxer + RSpec.describe Database, type: :service do + describe ".truncate" do + before do + message = Models::Message.create!( + status: Models::Message::Status::QUEUED, + messageable_type: "Event", + messageable_id: "1", + queued_at: Time.now.utc) + + Models::MessageCount.create!(status: "queued", partition: 0, value: 1) + Models::MessageTotal.create!(status: "queued", partition: 0, value: 1) + + exception = Models::Exception.create!(message_id: message.id, message_text: "Example error") + Models::Frame.create!(exception_id: exception.id, text: "frame", index: 0) + + publisher = Models::Publisher.create!(name: "pub-1", settings: {}, metrics: {}) + Models::Signal.create!(publisher_id: publisher.id, name: "SIG1", created_at: Time.now.utc) + end + + it "removes all records from all tables" do + Database.truncate + + expect(Models::Message.count).to eq(0) + expect(Models::MessageCount.count).to eq(0) + expect(Models::MessageTotal.count).to eq(0) + expect(Models::Exception.count).to eq(0) + expect(Models::Frame.count).to eq(0) + expect(Models::Publisher.count).to eq(0) + expect(Models::Signal.count).to eq(0) + end + + it "resets auto-increment IDs" do + Database.truncate + + message = Models::Message.create!( + status: Models::Message::Status::QUEUED, + messageable_type: "Event", + messageable_id: "1", + queued_at: Time.now.utc + ) + + expect(message.id).to eq(1) + end + end + end +end