# frozen_string_literal: true

require "cases/helper"

class SQLite3TransactionTest < ActiveRecord::SQLite3TestCase
  test "shared_cached? is true when cache-mode is enabled" do
    with_connection(flags: shared_cache_flags) do |conn|
      assert_predicate(conn, :shared_cache?)
    end
  end

  test "shared_cached? is false when cache-mode is disabled" do
    flags = ::SQLite3::Constants::Open::READWRITE | SQLite3::Constants::Open::CREATE

    with_connection(flags: flags) do |conn|
      assert_not_predicate(conn, :shared_cache?)
    end
  end

  test "raises when trying to open a transaction in a isolation level other than `read_uncommitted`" do
    with_connection do |conn|
      assert_raises(ActiveRecord::TransactionIsolationError) do
        conn.transaction(requires_new: true, isolation: :something) do
          conn.transaction_manager.materialize_transactions
        end
      end
    end
  end

  test "raises when trying to open a read_uncommitted transaction but shared-cache mode is turned off" do
    with_connection do |conn|
      error = assert_raises(StandardError) do
        conn.transaction(requires_new: true, isolation: :read_uncommitted) do
          conn.transaction_manager.materialize_transactions
        end
      end

      assert_match("You need to enable the shared-cache mode", error.message)
    end
  end

  test "opens a `read_uncommitted` transaction" do
    with_connection(flags: shared_cache_flags) do |conn1|
      conn1.create_table(:zines) { |t| t.column(:title, :string) } if in_memory_db?
      conn1.transaction do
        conn1.transaction_manager.materialize_transactions
        conn1.execute("INSERT INTO zines (title) VALUES ('foo')")

        with_connection(flags: shared_cache_flags) do |conn2|
          conn2.transaction(joinable: false, isolation: :read_uncommitted) do
            assert_not_empty(conn2.execute("SELECT * FROM zines WHERE title = 'foo'"))
          end
        end

        raise ActiveRecord::Rollback
      end
    end
  end

  test "reset the read_uncommitted PRAGMA when a transaction is rolled back" do
    with_connection(flags: shared_cache_flags) do |conn|
      conn.transaction(joinable: false, isolation: :read_uncommitted) do
        assert_not(read_uncommitted?(conn))
        conn.transaction_manager.materialize_transactions
        assert(read_uncommitted?(conn))

        raise ActiveRecord::Rollback
      end

      assert_not(read_uncommitted?(conn))
    end
  end

  test "reset the read_uncommitted PRAGMA when a transaction is committed" do
    with_connection(flags: shared_cache_flags) do |conn|
      conn.transaction(joinable: false, isolation: :read_uncommitted) do
        assert_not(read_uncommitted?(conn))
        conn.transaction_manager.materialize_transactions
        assert(read_uncommitted?(conn))
      end

      assert_not(read_uncommitted?(conn))
    end
  end

  test "set the read_uncommitted PRAGMA to its previous value" do
    with_connection(flags: shared_cache_flags) do |conn|
      conn.transaction(joinable: false, isolation: :read_uncommitted) do
        conn.instance_variable_get(:@connection).read_uncommitted = true
        assert(read_uncommitted?(conn))
        conn.transaction_manager.materialize_transactions
        assert(read_uncommitted?(conn))
      end

      assert(read_uncommitted?(conn))
    end
  end

  private
    def read_uncommitted?(conn)
      conn.instance_variable_get(:@connection).get_first_value("PRAGMA read_uncommitted") != 0
    end

    def shared_cache_flags
      ::SQLite3::Constants::Open::READWRITE | SQLite3::Constants::Open::CREATE | ::SQLite3::Constants::Open::SHAREDCACHE
    end

    def with_connection(options = {})
      options = options.dup
      if in_memory_db?
        options[:database] ||= "file::memory:"
        options[:flags] = options[:flags].to_i | ::SQLite3::Constants::Open::URI | ::SQLite3::Constants::Open::READWRITE
      else
        db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
        options[:database] ||= db_config.database
      end
      conn = ActiveRecord::Base.sqlite3_connection(options)

      yield(conn)
    ensure
      conn.disconnect! if conn
    end
end
