require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper.rb')

describe "Database transactions" do
  before do
    @db = INTEGRATION_DB
    @db.drop_table(:items) if @db.table_exists?(:items)
    @db.create_table(:items, :engine=>'InnoDB'){String :name; Integer :value}
    @d = @db[:items]
    clear_sqls
  end
  after do
    @db.drop_table(:items) if @db.table_exists?(:items)
  end

  specify "should support transactions" do
    @db.transaction{@d << {:name => 'abc', :value => 1}}
    @d.count.should == 1
  end

  specify "should have #transaction yield the connection" do
    @db.transaction{|conn| conn.should_not == nil}
  end

  specify "should correctly rollback transactions" do
    proc do
      @db.transaction do
        @d << {:name => 'abc', :value => 1}
        raise Interrupt, 'asdf'
      end
    end.should raise_error(Interrupt)

    proc do
      @db.transaction do
        @d << {:name => 'abc', :value => 1}
        raise Sequel::Rollback
      end
    end.should_not raise_error

    @d.count.should == 0
  end

  specify "should support nested transactions" do
    @db.transaction do
      @db.transaction do
        @d << {:name => 'abc', :value => 1}
      end 
    end 
    @d.count.should == 1

    @d.delete
    proc {@db.transaction do
      @d << {:name => 'abc', :value => 1}
      @db.transaction do
        raise Sequel::Rollback
      end 
    end}.should_not raise_error
    @d.count.should == 0

    proc {@db.transaction do
      @d << {:name => 'abc', :value => 1}
      @db.transaction do
        raise Interrupt, 'asdf'
      end 
    end}.should raise_error(Interrupt)
    @d.count.should == 0
  end 
  
  if INTEGRATION_DB.supports_savepoints?
    cspecify "should support nested transactions through savepoints using the savepoint option", [:jdbc, :sqlite] do
      @db.transaction do
        @d << {:name => '1'}
        @db.transaction(:savepoint=>true) do
          @d << {:name => '2'}
          @db.transaction do
            @d << {:name => '3'}
            raise Sequel::Rollback
          end
        end
        @d << {:name => '4'}
        @db.transaction do
          @d << {:name => '6'}
          @db.transaction(:savepoint=>true) do
            @d << {:name => '7'}
            raise Sequel::Rollback
          end
        end
        @d << {:name => '5'}
      end

      @d.order(:name).map(:name).should == %w{1 4 5 6}
    end
  end

  specify "should handle returning inside of the block by committing" do
    def @db.ret_commit
      transaction do
        self[:items] << {:name => 'abc'}
        return
        self[:items] << {:name => 'd'}
      end
    end

    @d.count.should == 0
    @db.ret_commit
    @d.count.should == 1
    @db.ret_commit
    @d.count.should == 2
    proc do
      @db.transaction do
        raise Interrupt, 'asdf'
      end
    end.should raise_error(Interrupt)

    @d.count.should == 2
  end

  if INTEGRATION_DB.supports_prepared_transactions?
    specify "should commit prepared transactions using commit_prepared_transaction" do
      @db.transaction(:prepare=>'XYZ'){@d << {:name => '1'}}
      @db.commit_prepared_transaction('XYZ')
      @d.select_order_map(:name).should == ['1']
    end

    specify "should rollback prepared transactions using rollback_prepared_transaction" do
      @db.transaction(:prepare=>'XYZ'){@d << {:name => '1'}}
      @db.rollback_prepared_transaction('XYZ')
      @d.select_order_map(:name).should == []
    end
  end

  specify "should support all transaction isolation levels" do
    [:uncommitted, :committed, :repeatable, :serializable].each_with_index do |l, i|
      @db.transaction(:isolation=>l){@d << {:name => 'abc', :value => 1}}
      @d.count.should == i + 1
    end
  end
end
