// Perf09.cpp : main project file.

using namespace System;
using namespace System::Diagnostics;
using namespace System::Threading;

/* Raima::Rdm is the system module that contains the ADO.NET
 * data provider for RDM */
using namespace Raima::Rdm;

/* EXAMPLE - Perf09 for C++ 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 standard
 * locks.  This can be compared against the performance in Perf10 where
 * the reading thread uses Read-Only Transactions.
 * 
 * The following methods are illustrated:
 *     Raima::Rdm::RdmConnection:
 *         ChangeDatabase(string) Method
 *         Close() Method
 *         CreateCommand() Method
 *         Open() Method
 *         RdmConnection() Constructor
 *     Raima::Rdm::RdmCommand:
 *         CommandText Property
 *         ExecuteNonQuery() Method
 * 
 * They are fully document in http://docs.raima.com/rdm/.
 * 
 * For simplicity this example does not catch any thrown execptions
 * but good programming practices would dictate that this occur in
 * a full application.
 */

#define NumRows 50000

static bool shutdown = false;
static int rowsUpdated = 0;
static int queriesExecuted = 0;

static const int transactionSize = 1000;

static void DropDatabase(
    RdmConnection ^conn,
    String        ^name)
{
    RdmCommand ^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();

        cmd->CommandText = "COMMIT";
        cmd->ExecuteNonQuery();
    }
    catch (Exception ^)
    {
    }
}

static void CreateThisDatabase(
    RdmConnection   ^conn,
    String          ^name,
    array<String ^> ^cmds)
{
    RdmCommand ^cmd = conn->CreateCommand();

    cmd->CommandText = "CREATE DATABASE " + name;
    cmd->ExecuteNonQuery();

    for (int ii = 0; ii < cmds->Length; ii++)
    {
        cmd->CommandText = cmds[ii];
        cmd->ExecuteNonQuery();
    }

    cmd->CommandText = "COMMIT";
    cmd->ExecuteNonQuery();

    cmd->CommandText = "CLOSE " + name;
    cmd->ExecuteNonQuery();
}

static void CreateDatabases(
    RdmConnection ^conn)
{
    array<String ^> ^cmds = {
        "CREATE TABLE Simple (id INTEGER PRIMARY KEY, int_col INTEGER)"
    };

    /* Drop any existing copy of the databases. */
    DropDatabase(conn, "Perf09");

    /* Attempt to create the databases */
    CreateThisDatabase(conn, "Perf09", cmds);
}

/* Add 50,000 rows to the table */
static void AddRows(RdmConnection ^conn)
{
    RdmCommand ^cmd = conn->CreateCommand();
    cmd->CommandText = "INSERT INTO Simple VALUES :id, 0";
    RdmParameter ^parm = cmd->Parameters->Add("id", RdmType::Int32);

    RdmTransaction ^trans = conn->BeginTransaction();

    for (int ii = 0; ii < NumRows; ii++)
    {
        parm->Value = ii;
        cmd->ExecuteNonQuery();
    }

    trans->Commit();
}

/* Initialization - Open the connection and create the database */
static void Initialize(RdmConnection ^conn)
{
    conn->Open();
    CreateDatabases(conn);
    conn->ChangeDatabase("Perf09");
    AddRows(conn);

    Console::WriteLine("Initializing with DIRECT-LINK configuration");
    Console::WriteLine("\tOne, simple row type");
    Console::WriteLine("\tOn disk");
    Console::WriteLine("\tOpened exclusively");
    Console::WriteLine();
    Console::WriteLine("\t\t\tHit <Enter> to exit the example");
}

/* Thread to add rows in chunks of 1000. */
static void UpdateRowsThread()
{
    RdmConnection ^conn = gcnew RdmConnection("host=local;database=Perf09;openmode=s;tfstype=tfst;autocommit=off");
    conn->Open();

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

    Console::WriteLine("Updating rows in transactions of size {0}", transactionSize);
    Console::WriteLine();

    int minid = 0;

    while (!shutdown)
    {
        if (minid >= NumRows)
            minid = 0;

        RdmTransaction ^trans = conn->BeginTransaction();

        minparm->Value = minid;
        cmd->ExecuteNonQuery();

        trans->Commit();

        rowsUpdated += transactionSize;
        minid += transactionSize;
    }

    Console::WriteLine("Ending updates");

    conn->Close();
}

/* Thread to query the database (read) */
static void ReaderThread()
{
    RdmConnection ^conn = gcnew RdmConnection("host=local;database=Perf09;openmode=s;tfstype=tfst;autocommit=off");
    conn->Open();

    RdmCommand ^cmd = conn->CreateCommand();
    cmd->CommandText = "SELECT SUM(int_col) FROM Simple";

    Console::WriteLine("Reading the contents of the table");

    while (!shutdown)
    {
        cmd->ExecuteScalar();
        queriesExecuted++;
    }

    Console::WriteLine("Ending reading");

    conn->Close();
}

/* Thread to report results periodically */
static void ReporterThread()
{
    for (int secs = 1; ; secs++)
    {
        Thread::Sleep(1000);

        if (shutdown)
            break;

        Console::WriteLine("Updated {0} rows ({1}/sec) and executed {2} queries ({3:F2}/sec) in {4} secs",
                rowsUpdated, (rowsUpdated / secs), queriesExecuted, ((double) queriesExecuted / secs), secs);
    }
}

int main(array<System::String ^> ^args)
{
    Console::WriteLine("Perf09 Example");
    Console::WriteLine();

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

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

    Console::WriteLine();

    /* 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 */
    Thread ^updater = gcnew Thread(gcnew ThreadStart(UpdateRowsThread));
    Thread ^reader = gcnew Thread(gcnew ThreadStart(ReaderThread));
    Thread ^reporter = gcnew Thread(gcnew ThreadStart(ReporterThread));

    /* Start all threads */
    updater->Start();
    reader->Start();
    reporter->Start();

    /* Wait for user to hit <Enter> */
    Console::ReadLine();

    /* Indicate to the threads that they should shut down */
    shutdown = true;

    /* Wait for each thread to finish */
    updater->Join();
    reader->Join();
    reporter->Join();

    Console::WriteLine();

    /* Cleanup */
    conn->Close();

    Console::WriteLine();
    Console::WriteLine("Hit <Enter> to continue");
    Console::ReadLine();

    return 0;
}
