// Path: sane-dbc-core/src/main/java/com/novarto/sanedbc/core/interpreter/SyncDbInterpreter.java
// public class SyncDbInterpreter
// {
//     private final Try0<Connection, SQLException> ds;
// 
//     /**
//      * Construct an interpreter, given a piece of code which knows how to spawn connections, e.g. a Data Source, Connection Pool,
//      * Driver Manager, etc.
//      * @param ds - the data source, which can spawn connections
//      */
//     public SyncDbInterpreter(Try0<Connection, SQLException> ds)
//     {
//         this.ds = ds;
//     }
// 
//     /**
//      * Attempt to run this operation and return its result
//      * @param doOp the operation to run
//      * @param <A> the result type of the operation
//      * @return the successful result
//      * @throws RuntimeException if an {@link SQLException} is encountered
//      */
//     public <A> A submit(DB<A> doOp)
//     {
//         try (Connection c = ds.f())
//         {
//             return doOp.run(c);
//         }
//         catch (SQLException e)
//         {
//             throw new RuntimeException(e);
//         }
// 
//     }
// 
//     /**
//      * Attempt to run this operation and return its result. The operation is run transactionally - i.e. if any error is
//      * encountered, the operation is rolled back.
//      * @param doOp the operation to run
//      * @param <A> the result type of the operation
//      * @return the successful result
//      * @throws RuntimeException if an {@link SQLException} is encountered
//      */
//     public <A> A transact(DB<A> doOp)
//     {
// 
//         return submit(transactional(doOp));
// 
//     }
// }
// 
// Path: sane-dbc-core/src/main/java/com/novarto/sanedbc/core/ops/DbOps.java
// public static <A> DB<Option<A>> unique(DB<? extends Iterable<A>> op)
// {
//     return op.map(xs ->
//     {
//         Iterator<A> it = xs.iterator();
//         if (!it.hasNext())
//         {
//             return none();
//         }
// 
//         A result = it.next();
// 
//         if (it.hasNext())
//         {
//             throw new RuntimeException("unique with more than one element");
//         }
//         return some(result);
//     });
// }


import com.novarto.sanedbc.core.interpreter.SyncDbInterpreter;
import com.novarto.sanedbc.core.ops.*;
import fj.Unit;
import fj.control.db.DB;
import fj.data.List;
import fj.data.Option;
import org.junit.Test;
import java.sql.DriverManager;
import static com.novarto.sanedbc.core.ops.DbOps.unique;
import static fj.data.List.nil;
import static java.util.Arrays.asList;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
                A mapper is a function which maps a single row from the resultset to a single object
                A generic SelectOp also needs to be told what collection to use in the result
                The SelectOp.FjList specialization uses fj.data.List, which is an immutable singly-linked list.
                (Also known as Cons list)
                There is also a specialization for java Lists; or you can supply your own collection builder
             */
            return new SelectOp.FjList<>(
                    "SELECT ID, DESCRIPTION FROM STUFF WHERE DESCRIPTION=?",
                    ps -> ps.setString(1, description),
                    //build a Stuff from a resultset row
                    rs -> new Stuff(rs.getInt(1), rs.getString(2))
                    );
        }

        /*
            Select an entry by id
            The return type is fj.data.Option, since it may be that no entry with this id exists
            fj.data.Option is equivalent to Java Optional
         */
        public static DB<Option<Stuff>> selectByKey(int id)
        {

            //given a regular operation which returns an iterable:
            DB<List<Stuff>> temp = new SelectOp.FjList<>(
                    "SELECT ID, DESCRIPTION FROM STUFF WHERE ID=?",
                    ps -> ps.setInt(1, id),
                    rs -> new Stuff(rs.getInt(1), rs.getString(2)));

            // using the unique() function,
            // we can convert it to an operation which expects at most one result, and returns that optional result:
// Path: sane-dbc-core/src/main/java/com/novarto/sanedbc/core/interpreter/SyncDbInterpreter.java
// public class SyncDbInterpreter
// {
//     private final Try0<Connection, SQLException> ds;
// 
//     /**
//      * Construct an interpreter, given a piece of code which knows how to spawn connections, e.g. a Data Source, Connection Pool,
//      * Driver Manager, etc.
//      * @param ds - the data source, which can spawn connections
//      */
//     public SyncDbInterpreter(Try0<Connection, SQLException> ds)
//     {
//         this.ds = ds;
//     }
// 
//     /**
//      * Attempt to run this operation and return its result
//      * @param doOp the operation to run
//      * @param <A> the result type of the operation
//      * @return the successful result
//      * @throws RuntimeException if an {@link SQLException} is encountered
//      */
//     public <A> A submit(DB<A> doOp)
//     {
//         try (Connection c = ds.f())
//         {
//             return doOp.run(c);
//         }
//         catch (SQLException e)
//         {
//             throw new RuntimeException(e);
//         }
// 
//     }
// 
//     /**
//      * Attempt to run this operation and return its result. The operation is run transactionally - i.e. if any error is
//      * encountered, the operation is rolled back.
//      * @param doOp the operation to run
//      * @param <A> the result type of the operation
//      * @return the successful result
//      * @throws RuntimeException if an {@link SQLException} is encountered
//      */
//     public <A> A transact(DB<A> doOp)
//     {
// 
//         return submit(transactional(doOp));
// 
//     }
// }
// 
// Path: sane-dbc-core/src/main/java/com/novarto/sanedbc/core/ops/DbOps.java
// public static <A> DB<Option<A>> unique(DB<? extends Iterable<A>> op)
// {
//     return op.map(xs ->
//     {
//         Iterator<A> it = xs.iterator();
//         if (!it.hasNext())
//         {
//             return none();
//         }
// 
//         A result = it.next();
// 
//         if (it.hasNext())
//         {
//             throw new RuntimeException("unique with more than one element");
//         }
//         return some(result);
//     });
// }


// Path: sane-dbc-examples/src/test/java/com/novarto/sanedbc/examples/BasicUsage.java
import com.novarto.sanedbc.core.interpreter.SyncDbInterpreter;
import com.novarto.sanedbc.core.ops.*;
import fj.Unit;
import fj.control.db.DB;
import fj.data.List;
import fj.data.Option;
import org.junit.Test;
import java.sql.DriverManager;
import static com.novarto.sanedbc.core.ops.DbOps.unique;
import static fj.data.List.nil;
import static java.util.Arrays.asList;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

                A mapper is a function which maps a single row from the resultset to a single object
                A generic SelectOp also needs to be told what collection to use in the result
                The SelectOp.FjList specialization uses fj.data.List, which is an immutable singly-linked list.
                (Also known as Cons list)
                There is also a specialization for java Lists; or you can supply your own collection builder
             */
            return new SelectOp.FjList<>(
                    "SELECT ID, DESCRIPTION FROM STUFF WHERE DESCRIPTION=?",
                    ps -> ps.setString(1, description),
                    //build a Stuff from a resultset row
                    rs -> new Stuff(rs.getInt(1), rs.getString(2))
                    );
        }

        /*
            Select an entry by id
            The return type is fj.data.Option, since it may be that no entry with this id exists
            fj.data.Option is equivalent to Java Optional
         */
        public static DB<Option<Stuff>> selectByKey(int id)
        {

            //given a regular operation which returns an iterable:
            DB<List<Stuff>> temp = new SelectOp.FjList<>(
                    "SELECT ID, DESCRIPTION FROM STUFF WHERE ID=?",
                    ps -> ps.setInt(1, id),
                    rs -> new Stuff(rs.getInt(1), rs.getString(2)));

            // using the unique() function,
            // we can convert it to an operation which expects at most one result, and returns that optional result:
            return unique(temp);