mirror of
https://github.com/discourse/discourse.git
synced 2024-12-02 07:15:49 +08:00
7c3a29c9d6
* Updates GitHub Actions * Switches from `bundler/inline` to an optional group in the `Gemfile` because the previous solution didn't work well with rspec * Adds the converter framework and tests * Allows loading private converters (see README) * Switches from multiple CLI tools to a single CLI * Makes DB connections reusable and adds a new abstraction for the `IntermediateDB` * `IntermediateDB` acts as an interface for IPC calls when a converter steps runs in parallel (forks). Only the main process writes to the DB. * Includes a simple example implementation of a converter for now.
177 lines
5.3 KiB
Ruby
177 lines
5.3 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.describe ::Migrations::Database::Connection do
|
|
def create_connection(**params)
|
|
Dir.mktmpdir do |storage_path|
|
|
db_path = File.join(storage_path, "test.db")
|
|
connection = described_class.new(path: db_path, **params)
|
|
|
|
return connection if !block_given?
|
|
|
|
begin
|
|
yield connection
|
|
ensure
|
|
connection.close if connection
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "class" do
|
|
subject(:connection) { create_connection }
|
|
|
|
after { connection.close }
|
|
|
|
it_behaves_like "a database connection"
|
|
end
|
|
|
|
describe ".open_database" do
|
|
it "creates a database at the given path " do
|
|
Dir.mktmpdir do |storage_path|
|
|
db_path = File.join(storage_path, "test.db")
|
|
db = described_class.open_database(path: db_path)
|
|
|
|
expect(File.exist?(db_path)).to be true
|
|
expect(db.pragma("journal_mode")).to eq("wal")
|
|
expect(db.pragma("locking_mode")).to eq("normal")
|
|
ensure
|
|
db.close if db
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#close" do
|
|
it "closes the underlying database" do
|
|
create_connection do |connection|
|
|
db = connection.db
|
|
connection.close
|
|
expect(db).to be_closed
|
|
end
|
|
end
|
|
|
|
it "closes cached prepared statements" do
|
|
cache_class = ::Migrations::Database::PreparedStatementCache
|
|
cache_double = instance_spy(cache_class)
|
|
allow(cache_class).to receive(:new).and_return(cache_double)
|
|
|
|
create_connection do |connection|
|
|
expect(cache_double).not_to have_received(:clear)
|
|
connection.close
|
|
expect(cache_double).to have_received(:clear).once
|
|
end
|
|
end
|
|
|
|
it "commits an active transaction" do
|
|
create_connection do |connection|
|
|
db = described_class.open_database(path: connection.path)
|
|
db.execute("CREATE TABLE foo (id INTEGER)")
|
|
|
|
connection.insert("INSERT INTO foo (id) VALUES (?)", [1])
|
|
connection.insert("INSERT INTO foo (id) VALUES (?)", [2])
|
|
expect(db.query_single_splat("SELECT COUNT(*) FROM foo")).to eq(0)
|
|
|
|
connection.close
|
|
expect(db.query_single_splat("SELECT COUNT(*) FROM foo")).to eq(2)
|
|
|
|
db.close
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#closed?" do
|
|
it "correctly reports if connection is closed" do
|
|
create_connection do |connection|
|
|
expect(connection.closed?).to be false
|
|
connection.close
|
|
expect(connection.closed?).to be true
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#insert" do
|
|
it "commits inserted rows when reaching `batch_size`" do
|
|
transaction_batch_size = 3
|
|
|
|
create_connection(transaction_batch_size:) do |connection|
|
|
db = described_class.open_database(path: connection.path)
|
|
db.execute("CREATE TABLE foo (id INTEGER)")
|
|
|
|
1.upto(10) do |index|
|
|
connection.insert("INSERT INTO foo (id) VALUES (?)", [index])
|
|
|
|
expected_count = index / transaction_batch_size * transaction_batch_size
|
|
expect(db.query_single_splat("SELECT COUNT(*) FROM foo")).to eq(expected_count)
|
|
end
|
|
|
|
db.close
|
|
end
|
|
end
|
|
|
|
it "works with one and more parameters" do
|
|
transaction_batch_size = 1
|
|
|
|
create_connection(transaction_batch_size:) do |connection|
|
|
db = described_class.open_database(path: connection.path)
|
|
db.execute("CREATE TABLE foo (id INTEGER)")
|
|
db.execute("CREATE TABLE bar (id INTEGER, name TEXT)")
|
|
|
|
connection.insert("INSERT INTO foo (id) VALUES (?)", [1])
|
|
connection.insert("INSERT INTO bar (id, name) VALUES (?, ?)", [1, "Alice"])
|
|
|
|
expect(db.query_splat("SELECT id FROM foo")).to contain_exactly(1)
|
|
expect(db.query("SELECT id, name FROM bar")).to contain_exactly({ id: 1, name: "Alice" })
|
|
|
|
db.close
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when `::Migrations::ForkManager.fork` is used" do
|
|
it "temporarily closes the connection while a process fork is created" do
|
|
create_connection do |connection|
|
|
expect(connection.closed?).to be false
|
|
|
|
connection.db.execute("CREATE TABLE foo (id INTEGER)")
|
|
connection.insert("INSERT INTO foo (id) VALUES (?)", [1])
|
|
expect(connection.db.query_splat("SELECT id FROM foo")).to contain_exactly(1)
|
|
|
|
db_before_fork = connection.db
|
|
|
|
::Migrations::ForkManager.fork do
|
|
expect(connection.closed?).to be true
|
|
expect(connection.db).to be_nil
|
|
end
|
|
|
|
expect(connection.closed?).to be false
|
|
expect(connection.db).to_not eq(db_before_fork)
|
|
|
|
connection.insert("INSERT INTO foo (id) VALUES (?)", [2])
|
|
expect(connection.db.query_splat("SELECT id FROM foo")).to contain_exactly(1, 2)
|
|
end
|
|
end
|
|
|
|
it "works with multiple forks" do
|
|
create_connection do |connection|
|
|
expect(connection.closed?).to be false
|
|
|
|
::Migrations::ForkManager.fork { expect(connection.closed?).to be true }
|
|
|
|
expect(connection.closed?).to be false
|
|
|
|
::Migrations::ForkManager.fork { expect(connection.closed?).to be true }
|
|
|
|
expect(connection.closed?).to be false
|
|
end
|
|
end
|
|
|
|
it "cleans up fork hooks when connection gets closed" do
|
|
expect(::Migrations::ForkManager.size).to eq(0)
|
|
|
|
create_connection do |connection|
|
|
expect(::Migrations::ForkManager.size).to eq(2)
|
|
connection.close
|
|
expect(::Migrations::ForkManager.size).to eq(0)
|
|
end
|
|
end
|
|
end
|
|
end
|