require "./spec_helper"

describe DB::Database do
  it "allows connection initialization" do
    cnn_setup = 0
    DB.open "dummy://localhost:1027?initial_pool_size=2&max_pool_size=4&max_idle_pool_size=1" do |db|
      cnn_setup.should eq(0)

      db.setup_connection do |cnn|
        cnn_setup += 1
        cnn.scalar("a").should eq "a"
      end

      cnn_setup.should eq(2)

      db.using_connection do
        cnn_setup.should eq(2)
        db.using_connection do
          cnn_setup.should eq(2)
          db.using_connection do
            cnn_setup.should eq(3)
            db.using_connection do
              cnn_setup.should eq(4)
            end
            # the pool didn't shrink no new initialization should be done next
            db.using_connection do
              cnn_setup.should eq(4)
            end
          end
          # the pool shrink 1. max_idle_pool_size=1
          # after the previous end there where 2.
          db.using_connection do
            cnn_setup.should eq(4)
            # so now there will be a new connection created
            db.using_connection do
              cnn_setup.should eq(5)
            end
          end
        end
      end
    end
  end

  it "should allow creation of more statements than pool connections" do
    DB.open "dummy://localhost:1027?initial_pool_size=1&max_pool_size=2" do |db|
      db.build("query1").should be_a(DB::PoolPreparedStatement)
      db.build("query2").should be_a(DB::PoolPreparedStatement)
      db.build("query3").should be_a(DB::PoolPreparedStatement)
    end
  end

  it "should return same statement in pool per query" do
    with_dummy do |db|
      stmt = db.build("query1")
      db.build("query2").should_not eq(stmt)
      db.build("query1").should eq(stmt)
    end
  end

  it "should not reconnect if connection is lost and retry_attempts=0" do
    DummyDriver::DummyConnection.clear_connections
    DB.open "dummy://localhost:1027?initial_pool_size=1&max_pool_size=1&retry_attempts=0" do |db|
      db.exec("stmt1")
      DummyDriver::DummyConnection.connections.size.should eq(1)
      DummyDriver::DummyConnection.connections.first.disconnect!
      expect_raises DB::PoolRetryAttemptsExceeded do
        db.exec("stmt1")
      end
      DummyDriver::DummyConnection.connections.size.should eq(1)
    end
  end

  it "should reconnect if connection is lost and executing same statement" do
    DummyDriver::DummyConnection.clear_connections
    DB.open "dummy://localhost:1027?initial_pool_size=1&max_pool_size=1&retry_attempts=1" do |db|
      db.exec("stmt1")
      DummyDriver::DummyConnection.connections.size.should eq(1)
      DummyDriver::DummyConnection.connections.first.disconnect!
      db.exec("stmt1")
      DummyDriver::DummyConnection.connections.size.should eq(2)
    end
  end

  it "should allow new connections if pool can increased and retry is not allowed" do
    DummyDriver::DummyConnection.clear_connections
    DB.open "dummy://localhost:1027?initial_pool_size=1&max_pool_size=2&retry_attempts=0" do |db|
      db.query("stmt1")
      DummyDriver::DummyConnection.connections.size.should eq(1)
      db.query("stmt1")
      DummyDriver::DummyConnection.connections.size.should eq(2)
    end
  end

  it "should not return connection to pool if checkout explicitly" do
    DummyDriver::DummyConnection.clear_connections
    DB.open "dummy://localhost:1027?initial_pool_size=1&max_pool_size=1&retry_attempts=0" do |db|
      the_cnn = uninitialized DB::Connection
      db.using_connection do |cnn|
        the_cnn = cnn
        db.pool.is_available?(cnn).should be_false
        3.times do
          cnn.exec("stmt1")
          db.pool.is_available?(cnn).should be_false
        end
      end
      db.pool.is_available?(the_cnn).should be_true
    end
  end

  it "should checkout different connections until they are released" do
    DummyDriver::DummyConnection.clear_connections
    DB.open "dummy://localhost:1027?initial_pool_size=1&max_pool_size=2&retry_attempts=0" do |db|
      the_first_cnn = uninitialized DB::Connection
      the_second_cnn = uninitialized DB::Connection

      the_first_cnn = db.checkout
      the_second_cnn = db.checkout
      the_second_cnn.should_not eq(the_first_cnn)
      db.pool.is_available?(the_first_cnn).should be_false
      db.pool.is_available?(the_second_cnn).should be_false

      the_first_cnn.release
      db.pool.is_available?(the_first_cnn).should be_true
      db.pool.is_available?(the_second_cnn).should be_false

      db.checkout.should eq(the_first_cnn)
      the_first_cnn.release
      the_second_cnn.release
    end
  end

  it "should not return explicit checked out connections to the pool after query" do
    DummyDriver::DummyConnection.clear_connections
    DB.open "dummy://localhost:1027?initial_pool_size=1&max_pool_size=2&retry_attempts=0" do |db|
      cnn = db.checkout

      cnn.query_all("1", as: String)

      db.pool.is_available?(cnn).should be_false
      cnn.release
      db.pool.is_available?(cnn).should be_true
    end
  end

  it "should return connection to the pool if prepared statement is unable to be built" do
    connection = uninitialized DB::Connection
    with_dummy "dummy://localhost:1027?initial_pool_size=1" do |db|
      connection = DummyDriver::DummyConnection.connections.first
      expect_raises DB::Error do
        db.prepared.exec("syntax error")
      end
      db.pool.is_available?(connection).should be_true
    end
  end

  it "should return connection to the pool if unprepared statement is unable to be built" do
    connection = uninitialized DB::Connection
    with_dummy "dummy://localhost:1027?initial_pool_size=1" do |db|
      connection = DummyDriver::DummyConnection.connections.first
      expect_raises DB::Error do
        db.unprepared.exec("syntax error")
      end
      db.pool.is_available?(connection).should be_true
    end
  end

  it "should close connection on ConnectionLost" do
    DummyDriver::DummyConnection.clear_connections
    DB.open "dummy://localhost:1027?initial_pool_size=1&max_pool_size=1&retry_attempts=1" do |db|
      db.exec("stmt1")
      DummyDriver::DummyConnection.connections.size.should eq(1)
      connection = DummyDriver::DummyConnection.connections.first
      connection.disconnect!
      connection.closed?.should be_false
      db.exec("stmt1")
      # A new connection was used for the last statement
      DummyDriver::DummyConnection.connections.size.should eq(2)
      connection.closed?.should be_true
    end
  end

  it "should not checkout multiple connections if there is a statement error" do
    with_dummy "dummy://localhost:1027?initial_pool_size=1&max_pool_size=10&retry_attempts=10" do |db|
      expect_raises DB::Error do
        db.exec("syntax error")
      end
      DummyDriver::DummyConnection.connections.size.should eq(1)
    end
  end

  it "should attempt all retries if connection is lost" do
    with_dummy "dummy://localhost:1027?initial_pool_size=1&max_pool_size=1&retry_attempts=10" do |db|
      expect_raises DB::PoolRetryAttemptsExceeded do
        db.exec("raise ConnectionLost")
      end
      # 1 initial + 10 retries
      DummyDriver::DummyConnection.connections.size.should eq(11)
    end
  end

  describe "prepared_statements connection option" do
    it "defaults to true" do
      with_dummy "dummy://localhost:1027" do |db|
        db.prepared_statements?.should be_true
      end
    end

    it "can be set to false" do
      with_dummy "dummy://localhost:1027?prepared_statements=false" do |db|
        db.prepared_statements?.should be_false
      end
    end

    it "is copied to connections (false)" do
      with_dummy "dummy://localhost:1027?prepared_statements=false&initial_pool_size=1" do |db|
        connection = DummyDriver::DummyConnection.connections.first
        connection.prepared_statements?.should be_false
      end
    end

    it "is copied to connections (true)" do
      with_dummy "dummy://localhost:1027?prepared_statements=true&initial_pool_size=1" do |db|
        connection = DummyDriver::DummyConnection.connections.first
        connection.prepared_statements?.should be_true
      end
    end

    it "should build prepared statements if true" do
      with_dummy "dummy://localhost:1027?prepared_statements=true" do |db|
        db.build("the query").should be_a(DB::PoolPreparedStatement)
      end
    end

    it "should build unprepared statements if false" do
      with_dummy "dummy://localhost:1027?prepared_statements=false" do |db|
        db.build("the query").should be_a(DB::PoolUnpreparedStatement)
      end
    end

    it "should be overrided by dsl" do
      with_dummy "dummy://localhost:1027?prepared_statements=true" do |db|
        stmt = db.unprepared.query("the query").statement.as(DummyDriver::DummyStatement)
        stmt.prepared?.should be_false
      end

      with_dummy "dummy://localhost:1027?prepared_statements=false" do |db|
        stmt = db.prepared.query("the query").statement.as(DummyDriver::DummyStatement)
        stmt.prepared?.should be_true
      end
    end
  end

  describe "unprepared statements in pool" do
    it "creating statements should not create new connections" do
      with_dummy "dummy://localhost:1027?initial_pool_size=1" do |db|
        stmt1 = db.unprepared.build("query1")
        stmt2 = db.unprepared.build("query2")
        DummyDriver::DummyConnection.connections.size.should eq(1)
      end
    end

    it "simultaneous statements should go to different connections" do
      with_dummy "dummy://localhost:1027?initial_pool_size=1" do |db|
        rs1 = db.unprepared.query("query1")
        rs2 = db.unprepared.query("query2")
        rs1.statement.connection.should_not eq(rs2.statement.connection)
        DummyDriver::DummyConnection.connections.size.should eq(2)
      end
    end

    it "sequential statements should go to different connections" do
      with_dummy "dummy://localhost:1027?initial_pool_size=1" do |db|
        rs1 = db.unprepared.query("query1")
        rs1.close
        rs2 = db.unprepared.query("query2")
        rs2.close
        rs1.statement.connection.should eq(rs2.statement.connection)
        DummyDriver::DummyConnection.connections.size.should eq(1)
      end
    end
  end
end
