open System.Diagnostics
open System.Threading

(* Raima.Rdm is the system module that contains the ADO.NET data provider
   for RDM *)
open Raima.Rdm

(* EXAMPLE - Perf10 for F# using ADO.NET *)
(* This simple performance example creates 50,000 rows in a simple table
 * in an on-disk database.  It then spawns three threads.  The first one
 * updates rows in the table inside transactions with 1000 updates per
 * transaction.  The second thread attempts to read all the records to 
 * sum their contents.  The third thread just periodically reports the
 * results from the other two threads.  The reading thread uses read only
 * transactions.  This can be compared against the performance in Perf09
 * where the reading thread uses standard locking.
 * 
 * The following methods are illustrated:
 *     Raima.Rdm.RdmConnection:
 *         ChangeDatabase(string) Method
 *         Close() Method
 *         CreateCommand() Method
 *         Open() Method
 *         RdmConnection(String) Constructor
 *     Raima.Rdm.RdmCommand:
 *         CommandText Property
 *         ExecuteNonQuery() Method
 *         ExecuteScalar() Method
 * 
 * They are fully document in http://docs.raima.com/rdm/.
 * 
 * For simplicity this example does not catch any thrown exceptions
 * but good programming practices would dictate that this occur in
 * a full application.
 *)

let NumRows = 50000
let transactionSize = 1000

let mutable shutdown = false
let mutable rowsUpdated = 0
let mutable queriesExecuted = 0

let DropDatabase (conn : RdmConnection) name =
    let cmd = conn.CreateCommand()

    (* Attempt to drop this database.  If the database does not already exist
     * this will fail and we simply ignore the failure for this sample. *)
    try
        cmd.CommandText <- "DROP DATABASE " + name
        cmd.ExecuteNonQuery() |> ignore
    with
        | exn -> ()

let CreateThisDatabase (conn : RdmConnection) name cmds =
    let cmd = conn.CreateCommand()

    cmd.CommandText <- "CREATE DATABASE " + name
    cmd.ExecuteNonQuery() |> ignore

    cmds |>
        Array.iter(fun c ->
            cmd.CommandText <- c
            cmd.ExecuteNonQuery() |> ignore
        )

    cmd.CommandText <- "COMMIT"
    cmd.ExecuteNonQuery() |> ignore

    cmd.CommandText <- "CLOSE " + name
    cmd.ExecuteNonQuery() |> ignore

let CreateDatabases conn =
    (* Drop any existing copies of the databases. *)
    DropDatabase conn "Perf10"

    (* Attempt to create the databases *)
    CreateThisDatabase conn "Perf10" [| "CREATE TABLE Simple (id INTEGER PRIMARY KEY, int_col INTEGER)" |]

(* Add 50,000 rows to the table *)
let AddRows (conn : RdmConnection) = 
    let cmd = conn.CreateCommand()
    cmd.CommandText <- "INSERT INTO Simple VALUES :id, 0"
    let parm = cmd.Parameters.Add("id", RdmType.Int32)

    let trans = conn.BeginTransaction()

    seq { 0 .. NumRows-1 } |>
        Seq.iter (fun ii ->
            parm.Value <- ii
            cmd.ExecuteNonQuery() |> ignore
        )

    trans.Commit()

(* Initialization - Open the connection and create the database *)
let Initialize (conn : RdmConnection) =
    conn.Open()
    CreateDatabases conn
    conn.ChangeDatabase("Perf10")
    AddRows conn
    
    printfn "Initializing with DIRECT-LINK configuration"
    printfn "\tOne, simple row type"
    printfn "\tOn disk"
    printfn "\tOpened exclusively"
    printfn ""
    printfn "\t\t\tHit <Enter> to exit the example"

let UpdateRowsThread _ =
    let conn = new RdmConnection("host=local;database=Perf10;openmode=s;tfstype=tfst;autocommit=off")
    conn.Open()

    let cmd = conn.CreateCommand()
    cmd.CommandText <- "UPDATE Simple SET int_col = int_col + 1 WHERE id >= :minid AND id < :minid + 1000"
    let minparm = cmd.Parameters.Add("minid", RdmType.Int32)

    printfn "Updating rows in transactions of size %d" transactionSize
    printfn ""

    Seq.unfold (fun minid ->
        match shutdown with
        | true -> None
        | false ->
            Some(minid,
                match minid with
                | z when z >= NumRows -> 0
                | _ -> minid + transactionSize)
        ) 0 |>
            Seq.iter(fun minid ->
                let trans = conn.BeginTransaction()

                minparm.Value <- minid
                cmd.ExecuteNonQuery() |> ignore

                trans.Commit()
                rowsUpdated <- rowsUpdated + transactionSize
            )

    printfn "Ending updates"

    conn.Close()

let ReaderThread _ =
    let conn = new RdmConnection("host=local;database=Perf10;openmode=s;tfstype=tfst;autocommit=off;readonlytransactions=on")
    conn.Open()

    let cmd = conn.CreateCommand()
    cmd.CommandText <- "SELECT SUM(int_col) FROM Simple"

    printfn "Reading the contents of the table"

    while shutdown = false do
        cmd.ExecuteScalar() |> ignore
        queriesExecuted <- queriesExecuted + 1

    printfn "Ending reading"

    conn.Close()

let ReporterThread _ =
    Seq.unfold (fun secs ->
        Thread.Sleep 1000
        match shutdown with
        | true -> None
        | false -> Some (secs, secs + 1)
    ) 1 |>
        Seq.iter(fun secs ->
            printfn "Updated %d rows (%d/sec) and executed %d queries (%.2f/sec) in %d secs"
                    rowsUpdated (rowsUpdated / secs) queriesExecuted
                    ((double queriesExecuted) / (double secs)) secs
        )

printfn "Perf10 Example"
printfn ""

(* Create the connection with a simple connection string *)
let conn = new RdmConnection "host=local;openmode=s;tfstype=tfst;autocommit=off"

(* Open the connection, delete any existing database, and create the database
 * anew *)
Initialize conn

printfn ""

(* Create three new threads - one to update records in the table
 * as fast as it can and another to read the contents of the table
 * and the third to periodically report the results *)
let updater = new Thread(new ThreadStart(UpdateRowsThread))
let reader = new Thread(new ThreadStart(ReaderThread))
let reporter = new Thread(new ThreadStart(ReporterThread))

(* Start all threads *)
updater.Start()
reader.Start()
reporter.Start()

(* Wait for user to hit <Enter> *)
System.Console.ReadLine() |> ignore

(* Indicate to the threads that they should shut down *)
shutdown <- true

(* Wait for each thread to finish *)
updater.Join()
reader.Join()
reporter.Join()

printfn ""

conn.Close()

printfn ""
printfn "Hit <Enter> to continue"
System.Console.ReadLine() |> ignore

