/* ----------------------------------------------------------------------------
 *
 * Raima Database Manager
 *
 * Copyright (c) 2012 Raima Inc.,  All rights reserved.
 *
 * Use of this software, whether in source code format, or in executable,
 * binary object code form, is governed by the Raima LICENSE which
 * is fully described in the LICENSE.TXT file, included within this
 * distribution of files.
 *
 * --------------------------------------------------------------------------*/
#include <stdio.h>

/* rdm.h is required in all files using the RDM Database */
#include "rdm.h"

/* perf05_db.h was generated from the DDL file perf05_db.sdl. */
#include "perf05_db.h"

/* perf05_db_dbd.h was generated from the DDL file perf05_db.sdl. using rdmsql with the -c option */
#include "perf05_db_dbd.h"

/*! Number of records to insert */
#define NUM_RECORDS 50000

/*! \file perf05.c
 * \brief Source code for the RDM Perf05 example
 */
 
/*!
 * \mainpage Perf05
 * \author Jeffrey R. Parsons, Raima Sr. Engineer
 * \date 2012
 *
 * \section Description
 * EXAMPLE - Perf05
 *
 * The Perf05 example performs a series of insert operations with varying
 * transaction block sizes.  The inserts are into very simple database schema using
 * the RDM Core C APIs.
 *
 * Each time you run this example
 *   * The database is initialized (removing all existing data)
 *   * 50,000 new records are created using different transaction block sizes
 *       * 50,000
         * 10,000
         *  5,000
         *  1,000
         *    500
         *    100
 *   * Database is cleaned up
 *
 * \section Perf05 Database
 * The DDL (Database Definition Language) specification for the database used
 * in this example is located in the file perf05_db.sdl. The example utilizes 
 * the SQL DDL syntax and specifies an in-memory database with a single 
 * table (simple).  The table has a single column (int_col).  There are no 
 * index definitions for this database.
 *
 * \include perf05_db.sdl
 *
 * The schema was compiled using the RDM rdmsql utility with the -c option to
 * generate an embedded database dictionary (used by the RDM d_open_ptr API).
 *
 * \code rdmsql -c perf05_db_db.sdl \endcode
 *
 * The database dictionary information is embedded inside the application so 
 * an external DBD file stored on the file system is not required.
 *
 * \section RDM Functions Used
 * The following RDM functions are illustrated:
 *   * d_tfsinitEx/d_tfsterm
 *   * d_opentask/d_closetask
 *   * d_initialize
 *   * d_open/d_close
 *   * d_fillnew
 *   * d_trbegin/d_trend
 *   * d_errorname
 *   * d_errorstr
 *
 * They are fully documented in http://docs.raima.com/rdm/.
 */

/*! \brief Display a message and the elapsed time to perform database operations
 *
 */
void display_elapsed_time(
    const char *msg,         /**< [in] Message to be display along with the elapsed time */
    uint64_t    start_time,  /**< [in] Start of elapsed time to display */
    uint64_t    end_time)    /**< [in] End of elapsed */
{
    uint64_t elapsed_time = end_time - start_time;
    printf("%s" UINT64_SPEC " millisecs\n", msg, elapsed_time/1000000);
}

/*! \brief Initialize the RDM runtime library for use in the perf05 example
 *
 * This function initializes the RDM Transactional File Server (TFS) to use 
 * the direct-link transactional TFS implementation.  It also creates
 * an RDM runtime task and opens the "perf05_db" database in exclusive ('x') mode.
 * Exclusve mode does not require database locks or transactions.
 *
 * @return Returns a 32-bit RDM return code (S_OKAY if successful)
 */
int32_t initialize(
    TFS_HANDLE  *hTFS,   /**< [out] Pointer to the TFS handle to be initialized */
    DB_TASK    **task)   /**< [out] Pointer to the RDM task to be initialized */
{
    int32_t  rdm_status = S_OKAY;
    uint64_t start_time;
    uint64_t end_time;    

    /* Initialize the TFS to use the DIRECT-LINK configuration (TFS) */    
    start_time = psp_timeNanoSecs();
    rdm_status = d_tfsinitEx(NULL, NULL, hTFS, TFS_TYPE_TFS);
    if(rdm_status != S_OKAY)
    {
        fprintf(stderr, "Error initializing the TFS (%s - %s)\n", d_errorname(rdm_status), d_errorstr(rdm_status));
        return rdm_status;
    }

    /* Create the RDM DB_TASK */
    rdm_status = d_opentask(task);
    if(rdm_status != S_OKAY)
    {
        fprintf(stderr, "Error opening the RDM task (%s - %s)\n", d_errorname(rdm_status), d_errorstr(rdm_status));
        return rdm_status;
    }

    /* Open the perf05_db database in exclusive mode (does not require locks or tranactions) */
    rdm_status = d_open_ptr("perf05_db", "x", &perf05_db_dbd, sizeof(perf05_db_dbd), *task);
    if(rdm_status != S_OKAY)
    {
        fprintf(stderr, "Error opening the perf05_db database (%s - %s)\n", d_errorname(rdm_status), d_errorstr(rdm_status));
        return rdm_status;
    }

    /* Initialize the database (creates new database files truncating any existing data) */    
    rdm_status = d_initialize(*task, CURR_DB);
    if(rdm_status != S_OKAY)
    {
        fprintf(stderr, "Error initializing the perf05_db database (%s - %s)\n", d_errorname(rdm_status), d_errorstr(rdm_status));
        return rdm_status;
    }
    end_time = psp_timeNanoSecs();  
    
    /* Display information */
    printf("\n");
    printf("Initializing with DIRECT-LINK configuration\n");
    display_elapsed_time("Preparing a new database:\t\t\t", start_time, end_time);
    printf("\tOne, simple record type.\n");
    printf("\tIn-memory\n");
    printf("\tOpened exclusively\n");

    return rdm_status;
}

/*! \brief Cleanup the RDM runtime library
 *
 * This functions closes all open databases and cleans up the RDM runtime task
 * used in the perf05 example.  It also terminates the TFS used
 * in the example. 
 *
 * @return Returns a 32-bit RDM return code (S_OKAY if successful)
 */
void cleanup(
    TFS_HANDLE  hTFS,   /**< [out] Pointer to the TFS handle to be terminated */
    DB_TASK    *task)   /**< [out] Pointer to the RDM task to be cleaned up */
{
    uint64_t start_time;
    uint64_t end_time;

    start_time = psp_timeNanoSecs();
    if(task)
    {
        /* Close any open databases */
        d_close(task);

        /* Close the task */
        d_closetask(task);
    }

    /* Terminate the TFS */
    d_tfsterm(hTFS);

    end_time = psp_timeNanoSecs();

    printf("\n");
    display_elapsed_time("Cleanup:\t\t\t\t\t", start_time, end_time);
}

/*! \brief Insert 50,000 simple records into the perf05 database
 *
 * This function adds 50,000 simple records into the inmemory database.  The
 * simple record contains only a single integer field with no index.  The function will
 * calculate and display the total time elapsed while inserting the records
 *
 * @return Returns a 32-bit RDM return code (S_OKAY if successful)
 */
int32_t add_records(
    int32_t  transaction_size,  /**< [in] Number of inserts to perform per transaction */
    DB_TASK *task)              /**< [in] RDM task initialized with the perf05 database open and as the current database */
{
    int32_t       rdm_status = S_OKAY;
    int32_t       ii;
    int32_t       count = 0;
    struct simple simple_rec;
    uint64_t      start_time;
    uint64_t      end_time;

    memset(&simple_rec, 0, sizeof(simple_rec));

    printf("Add %d records with %-5d per transaction:\t", NUM_RECORDS, transaction_size);

    start_time = psp_timeNanoSecs();
    
    /* Loop to add new simple record */
    for (ii = 0; ii < NUM_RECORDS; ii++)
    {
        /* Start a transaction if needed */
        if (count == 0)
        {
            rdm_status = d_trbegin("Insert", task);
            if (rdm_status != S_OKAY)
            {
                fprintf(stderr, "Error starting a RDM transaction (%s - %s)\n", d_errorname(rdm_status), d_errorstr(rdm_status));
                return rdm_status;
            }
        }

        /* Set values for the new record */
        simple_rec.int_col = 1;

        /* Add the record to the database */
        rdm_status = d_fillnew(SIMPLE, &simple_rec, task, CURR_DB);
        if (rdm_status != S_OKAY)
        {
            fprintf(stderr, "Error adding a simple record to the perf05 database (%s - %s)\n", d_errorname(rdm_status), d_errorstr(rdm_status));
            return rdm_status;
        }

        /* Commit the transaction if needed */
        if (++count == transaction_size)
        {
            rdm_status = d_trend(task);
            if (rdm_status != S_OKAY)
            {
                fprintf(stderr, "Error committing a RDM transaction (%s - %s)\n", d_errorname(rdm_status), d_errorstr(rdm_status));
                return rdm_status;
            }
            count = 0;
        }
    }
    end_time = psp_timeNanoSecs();

    printf("%-5d millisecs\n", ((end_time - start_time) / 1000000) );
    return rdm_status;
}

/*! \brief Main function for perf05 example
 *
 * The function initializes the RDM environment and runs the insert operations.
 * over a number of transaction block sizes
 *
 * @return Returns 0 on success, and 1 if there were any errors
 */
int32_t main()
{
    DB_TASK    *task;
    TFS_HANDLE  hTFS;
    int32_t     rdm_status;
    int32_t     ii;
    int32_t     trans_size[] = {50000, 10000, 5000, 1000, 500, 100};

    /* Display the example title */
    printf("%s", RDM_EXAMPLE_DESC("Perf05"));

    /* Initialize the TFS, task, and open/initialize the database */
    rdm_status = initialize(&hTFS, &task);
    if(rdm_status != S_OKAY)
    {
        fprintf(stderr, "Application is aborting due to initialization error\n");
        cleanup(hTFS, task);
        return 1;
    }
    
    printf("\n");
    for (ii=0; ii<RLEN(trans_size); ii++)
    {

        /* Add records to the database */
        rdm_status = add_records(trans_size[ii], task);
        if(rdm_status != S_OKAY)
        {
            fprintf(stderr, "Application is aborting due to error adding records\n");
            cleanup(hTFS, task);
            return 1;
        }

        /* Reinitialize the database (remove all existing data) */
        rdm_status = d_initialize(task, CURR_DB);
        if(rdm_status != S_OKAY)
        {
            fprintf(stderr, "Application is aborting due to error initializing the database\n");
            cleanup(hTFS, task);
            return 1;
        }
    }

    cleanup(hTFS, task);

    printf("\nHit <Enter> to continue . . .");
    getchar();

	return 0;
}
