// Path: src/main/java/com/pgasync/ResultSet.java
// public interface ResultSet extends Iterable<Row> {
// 
//     Map<String, PgColumn> getColumnsByName();
// 
//     List<PgColumn> getOrderedColumns();
// 
//     /**
//      * @param index Row index starting from 0
//      * @return Row, never null
//      */
//     Row at(int index);
// 
//     /**
//      * @return Amount of result rows.
//      */
//     int size();
// 
//     /**
//      * @return Amount of modified rows.
//      */
//     int affectedRows();
// 
// }
// 
// Path: src/main/java/com/pgasync/SqlException.java
// public class SqlException extends RuntimeException {
// 
//     private static final long serialVersionUID = 1L;
//     private static final int MAX_CAUSE_DEPTH = 100;
// 
//     final String code;
// 
//     public SqlException(String level, String code, String message) {
//         super(level + ": SQLSTATE=" + code + ", MESSAGE=" + message);
//         this.code = code;
//     }
// 
//     public SqlException(String message) {
//         super(message);
//         this.code = null;
//     }
// 
//     public SqlException(Throwable cause) {
//         super(cause);
//         this.code = null;
//     }
// 
//     public String getCode() {
//         return code;
//     }
// 
//     @FunctionalInterface
//     public interface CheckedRunnable {
// 
//         void run() throws Exception;
//     }
// 
//     public static boolean ifCause(Throwable th, Consumer<SqlException> action, CheckedRunnable others) throws Exception {
//         int depth = 1;
//         while (depth++ < MAX_CAUSE_DEPTH && th != null && !(th instanceof SqlException)) {
//             th = th.getCause();
//         }
//         if (th instanceof SqlException) {
//             action.accept((SqlException) th);
//             return true;
//         } else {
//             others.run();
//             return false;
//         }
//     }
// }


import com.pgasync.ResultSet;
import com.pgasync.SqlException;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import static org.junit.Assert.assertEquals;
                .get(5, TimeUnit.SECONDS);
        assertEquals(35L, id);
    }

    @Test
    public void shouldRollbackTransaction() throws Exception {
        dbr.pool().begin()
                .thenApply(transaction ->
                        transaction.completeQuery("INSERT INTO TX_TEST(ID) VALUES(9)")
                                .thenApply(result -> {
                                    Assert.assertEquals(1, result.affectedRows());
                                    return transaction.rollback();
                                })
                                .thenCompose(Function.identity())
                                .handle((v, th) -> transaction.getConnection().close()
                                        .thenAccept(_v -> {
                                            if (th != null) {
                                                throw new RuntimeException(th);
                                            }
                                        })
                                )
                                .thenCompose(Function.identity())
                )
                .thenCompose(Function.identity())
                .get(5, TimeUnit.SECONDS);

        Assert.assertEquals(0L, dbr.query("SELECT ID FROM TX_TEST WHERE ID = 9").size());
    }


// Path: src/main/java/com/pgasync/ResultSet.java
// public interface ResultSet extends Iterable<Row> {
// 
//     Map<String, PgColumn> getColumnsByName();
// 
//     List<PgColumn> getOrderedColumns();
// 
//     /**
//      * @param index Row index starting from 0
//      * @return Row, never null
//      */
//     Row at(int index);
// 
//     /**
//      * @return Amount of result rows.
//      */
//     int size();
// 
//     /**
//      * @return Amount of modified rows.
//      */
//     int affectedRows();
// 
// }
// 
// Path: src/main/java/com/pgasync/SqlException.java
// public class SqlException extends RuntimeException {
// 
//     private static final long serialVersionUID = 1L;
//     private static final int MAX_CAUSE_DEPTH = 100;
// 
//     final String code;
// 
//     public SqlException(String level, String code, String message) {
//         super(level + ": SQLSTATE=" + code + ", MESSAGE=" + message);
//         this.code = code;
//     }
// 
//     public SqlException(String message) {
//         super(message);
//         this.code = null;
//     }
// 
//     public SqlException(Throwable cause) {
//         super(cause);
//         this.code = null;
//     }
// 
//     public String getCode() {
//         return code;
//     }
// 
//     @FunctionalInterface
//     public interface CheckedRunnable {
// 
//         void run() throws Exception;
//     }
// 
//     public static boolean ifCause(Throwable th, Consumer<SqlException> action, CheckedRunnable others) throws Exception {
//         int depth = 1;
//         while (depth++ < MAX_CAUSE_DEPTH && th != null && !(th instanceof SqlException)) {
//             th = th.getCause();
//         }
//         if (th instanceof SqlException) {
//             action.accept((SqlException) th);
//             return true;
//         } else {
//             others.run();
//             return false;
//         }
//     }
// }


// Path: src/test/java/com/github/pgasync/TransactionTest.java
import com.pgasync.ResultSet;
import com.pgasync.SqlException;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import static org.junit.Assert.assertEquals;

                .get(5, TimeUnit.SECONDS);
        assertEquals(35L, id);
    }

    @Test
    public void shouldRollbackTransaction() throws Exception {
        dbr.pool().begin()
                .thenApply(transaction ->
                        transaction.completeQuery("INSERT INTO TX_TEST(ID) VALUES(9)")
                                .thenApply(result -> {
                                    Assert.assertEquals(1, result.affectedRows());
                                    return transaction.rollback();
                                })
                                .thenCompose(Function.identity())
                                .handle((v, th) -> transaction.getConnection().close()
                                        .thenAccept(_v -> {
                                            if (th != null) {
                                                throw new RuntimeException(th);
                                            }
                                        })
                                )
                                .thenCompose(Function.identity())
                )
                .thenCompose(Function.identity())
                .get(5, TimeUnit.SECONDS);

        Assert.assertEquals(0L, dbr.query("SELECT ID FROM TX_TEST WHERE ID = 9").size());
    }


    @Test(expected = SqlException.class)