#include "ocilib.h"

/* Direct path complete test suite demonstrating the following workflows :

- Normal loads
- Loads with conversion errors
- Loads with loading errors
- Loads with insufficient buffers
- Loads using force mode
*/

#define SIZE_ARRAY 10
#define NB_LOAD    10
#define SIZE_COL1  20
#define SIZE_COL2  30
#define SIZE_COL3  8
#define NUM_COLS   3
#define TABLE_NAME "test_dp"
#define CONST_NAME "test_dp_unique"
#define NB_ERROR   2
#define NB_TOTAL    (SIZE_ARRAY * NB_LOAD)

static boolean partionning_enabled = TRUE;

void create_table(OCI_Connection *con);
void drop_table(OCI_Connection *con);
void do_load(OCI_Connection *con, boolean gen_conv_error, boolean gen_load_error, boolean gen_buffer_error, boolean force, char *test_name);
void check_data(OCI_Connection *con, OCI_DirPath *dp, boolean gen_conv_error, boolean gen_load_error, boolean force);
void check_results(OCI_Connection *con, OCI_DirPath *dp, int expected, int result, char *str);
void cleanup(OCI_Connection *con, OCI_DirPath *dp);

int main(void)
{
    OCI_Connection *con = NULL;

    if (!OCI_Initialize(NULL, NULL, OCI_ENV_DEFAULT))
    {
        return EXIT_FAILURE;
    }

    con = OCI_ConnectionCreate("db", "usr", "pwd", OCI_SESSION_DEFAULT);

    /* Nominal test */
    do_load(con, FALSE, FALSE, FALSE, FALSE, "TEST BASE         - DEFAULT MODE");

    /* insufficient stream buffer size tests */
    do_load(con, FALSE, FALSE, TRUE, FALSE, "TEST SMALL BUFFER - DEFAULT MODE");
    do_load(con, FALSE, FALSE, TRUE, TRUE, "TEST SMALL BUFFER - FORCE   MODE");

    /* conversion error tests */
    do_load(con, TRUE, FALSE, FALSE, FALSE, "TEST CONV   ERROR - DEFAULT MODE");
    do_load(con, TRUE, FALSE, FALSE, TRUE, "TEST CONV   ERROR - FORCE   MODE");

    /* loading error tests */
    if (partionning_enabled)
    {
        do_load(con, FALSE, TRUE, FALSE, FALSE, "TEST LOAD   ERROR - DEFAULT MODE");
        do_load(con, FALSE, TRUE, FALSE, TRUE, "TEST LOAD   ERROR - FORCE   MODE");
    }

    OCI_ConnectionFree(con);
    OCI_Cleanup();

    return EXIT_SUCCESS;
}

void create_table(OCI_Connection *con)
{
    /* create a partitioned table (for loading error) */
    boolean res = OCI_ImmediateFmt(con, "create table %m(val_int int not null, val_str varchar2(30), val_date date) partition by range(val_int) ( partition test_dp_1 values less than (501),  partition test_dp_2 values less than (1001))", TABLE_NAME);

    /* in case partitioning is not available, create a regular table and disable loading errtests */
    if (!res)
    {
        partionning_enabled = FALSE;
        res = OCI_ImmediateFmt(con, "create table %m(val_int int not null, val_str varchar2(30), val_date date)", TABLE_NAME);
    }

    /* exit tests if table cannot be created */
    if (!res)
    {
        printf("FAILED : table cannot be created\n");
        drop_table(con);
        OCI_ConnectionFree(con);
        OCI_Cleanup();
        exit(EXIT_FAILURE);
    }
}

void drop_table(OCI_Connection *con)
{
    OCI_ImmediateFmt(con, "drop table %m", TABLE_NAME);
}

void cleanup(OCI_Connection *con, OCI_DirPath *dp)
{
    OCI_DirPathFree(dp);
    OCI_ConnectionFree(con);
    OCI_Cleanup();
}

void check_results(OCI_Connection *con, OCI_DirPath *dp, int expected, int result, char *str)
{
    if (result != expected)
    {
        printf("FAILED : %s - expected  %d - result %d\n", str, expected, result);
        OCI_DirPathFree(dp);
        drop_table(con);
        OCI_ConnectionFree(con);
        OCI_Cleanup();
        exit(EXIT_FAILURE);
    }
}

void check_data(OCI_Connection *con, OCI_DirPath *dp, boolean gen_conv_error, boolean gen_load_error, boolean force)
{
    int nb_rows = 0;

    OCI_ImmediateFmt(con, "select count(*) from %m", TABLE_NAME, OCI_ARG_INT, &nb_rows);
    check_results(con, dp, ((gen_conv_error && force) || gen_load_error) ? (NB_TOTAL - NB_ERROR) : NB_TOTAL, nb_rows, "total rows in database");

    OCI_ImmediateFmt(con, "select count(distinct val_int) from %m", TABLE_NAME, OCI_ARG_INT, &nb_rows);
    check_results(con, dp, ((gen_conv_error && force) || gen_load_error) ? (NB_TOTAL - NB_ERROR) : NB_TOTAL, nb_rows, "distinct rows in database");
}

void do_load(OCI_Connection *con, boolean gen_conv_error, boolean gen_load_error, boolean gen_buffer_error, boolean force, char *test_name)
{
    OCI_DirPath    *dp;

    char val1[SIZE_COL1 + 1];
    char val2[SIZE_COL2 + 1];
    char val3[SIZE_COL3 + 1];

    int i = 0, j = 0, nb_rows = SIZE_ARRAY;

    printf("%s : ", test_name);

    /* create table */
    create_table(con);

    /* create direct object */
    dp = OCI_DirPathCreate(OCI_TypeInfoGet(con, TABLE_NAME, OCI_TIF_TABLE), NULL, NUM_COLS, nb_rows);

    /* optional attributes to set */
    OCI_DirPathSetBufferSize(dp, gen_buffer_error ? 100 : 64000);
    OCI_DirPathSetConvertMode(dp, force ? OCI_DCM_FORCE : OCI_DCM_DEFAULT);

    /* describe the target table */
    OCI_DirPathSetColumn(dp, 1, "VAL_INT", SIZE_COL1, NULL);
    OCI_DirPathSetColumn(dp, 2, "VAL_STR", SIZE_COL2, NULL);
    OCI_DirPathSetColumn(dp, 3, "VAL_DATE", SIZE_COL3, "YYYYMMDD");

    /* prepare the load */
    OCI_DirPathPrepare(dp);

    /* perform the load - NB_LOAD iterations of SIZE_ARRAY rows */
    nb_rows = OCI_DirPathGetMaxRows(dp);

    for (i = 0; i < NB_LOAD; i++)
    {
        /* reset the load at each iteration */
        OCI_DirPathReset(dp);

        /* set the values for the current load iteration */
        for (j = 1; j <= nb_rows; j++)
        {
            /* conversion and loading errors are performed on the first load for rows 5 and 8 */
            if (i == 0 && (j == 5 || j == 8) && gen_conv_error)
            {
                /* generate a conversion error - not null clause violation on column val_int  */
                OCI_DirPathSetEntry(dp, j, 1, NULL, 0, TRUE);
                OCI_DirPathSetEntry(dp, j, 2, NULL, 0, TRUE);
                OCI_DirPathSetEntry(dp, j, 3, NULL, 0, TRUE);
            }
            else if (i == 0 && (j == 5 || j == 8) && gen_load_error)
            {
                /* generate a load error - if partitioning available, insert value out of declared partitions ranges */
                sprintf(val1, "%04d", 2500);
                sprintf(val2, "value %05d", j + (i*SIZE_ARRAY));
                sprintf(val3, "%04d%02d%02d", (j % 23) + 1 + 2000, (j % 11) + 1, (j % 23) + 1);

                OCI_DirPathSetEntry(dp, j, 1, val1, (unsigned int)strlen(val1), TRUE);
                OCI_DirPathSetEntry(dp, j, 2, val2, (unsigned int)strlen(val2), TRUE);
                OCI_DirPathSetEntry(dp, j, 3, val3, (unsigned int)strlen(val3), TRUE);
            }
            else
            {
                /* default loading */
                sprintf(val1, "%04d", j + (i*SIZE_ARRAY));
                sprintf(val2, "value %05d", j + (i*SIZE_ARRAY));
                sprintf(val3, "%04d%02d%02d", (j % 23) + 1 + 2000, (j % 11) + 1, (j % 23) + 1);

                OCI_DirPathSetEntry(dp, j, 1, val1, (unsigned int)strlen(val1), TRUE);
                OCI_DirPathSetEntry(dp, j, 2, val2, (unsigned int)strlen(val2), TRUE);
                OCI_DirPathSetEntry(dp, j, 3, val3, (unsigned int)strlen(val3), TRUE);
            }
        }

        /* load data to the server */
        while (TRUE)
        {
            int nb_conv = 0;
            int nb_error = 0;

            /* convert data*/
            int state = OCI_DirPathConvert(dp);

            /* retrieve the number of converted rows */
            nb_conv += OCI_DirPathGetAffectedRows(dp);

            /* conversion or loading errors are performed on the first load */
            if (i == 0)
            {
                if (gen_conv_error && !force && state == OCI_DPR_ERROR)
                {
                    /* on conversion error in default mode, correct values for next conversion */
                    int row = OCI_DirPathGetErrorRow(dp);

                    sprintf(val1, "%04d", row);
                    sprintf(val2, "value %05d", row);
                    sprintf(val3, "%04d%02d%02d", (row % 23) + 1 + 2000, (row % 11) + 1, (row % 23) + 1);

                    OCI_DirPathSetEntry(dp, row, 1, val1, (unsigned int)strlen(val1), TRUE);
                    OCI_DirPathSetEntry(dp, row, 2, val2, (unsigned int)strlen(val2), TRUE);
                    OCI_DirPathSetEntry(dp, row, 3, val3, (unsigned int)strlen(val3), TRUE);

                    nb_conv = 0;
                }

                if (gen_conv_error && force && state == OCI_DPR_COMPLETE)
                {
                    /* on conversion error in force mode, check we got the expected errors*/
                    int err_row = OCI_DirPathGetErrorRow(dp);
                    int err_col = OCI_DirPathGetErrorColumn(dp);

                    while (err_row != 0)
                    {
                        check_results(con, dp, nb_error == 0 ? 5 : 8, err_row, "erred converted row index (force mode)");
                        check_results(con, dp, err_col, 1, "erred column index in force mode");

                        err_row = OCI_DirPathGetErrorRow(dp);
                        err_col = OCI_DirPathGetErrorColumn(dp);

                        nb_error++;
                    }

                    check_results(con, dp, NB_ERROR, nb_error, "conversion errors in force mode");
                }
            }

            if (state == OCI_DPR_FULL)
            {
                /* buffer is too small - load the stream */

                state = OCI_DirPathLoad(dp);

                /* as the stream cannot accept all rows in one go,
                we need to do conversion+load until all rows are processed */

                while (nb_conv < SIZE_ARRAY)
                {
                    for (j = 1; j <= nb_rows - nb_conv; j++)
                    {
                        sprintf(val1, "%04d", (j + nb_conv) + (i*SIZE_ARRAY));
                        sprintf(val2, "value %05d", (j + nb_conv) + (i*SIZE_ARRAY));
                        sprintf(val3, "%04d%02d%02d", ((j + nb_conv) % 23) + 1 + 2000, ((j + nb_conv) % 11) + 1, ((j + nb_conv) % 23) + 1);

                        OCI_DirPathSetEntry(dp, j, 1, val1, (unsigned int)strlen(val1), TRUE);
                        OCI_DirPathSetEntry(dp, j, 2, val2, (unsigned int)strlen(val2), TRUE);
                        OCI_DirPathSetEntry(dp, j, 3, val3, (unsigned int)strlen(val3), TRUE);
                    }

                    /* convert again */

                    state = OCI_DirPathConvert(dp);

                    nb_conv = OCI_DirPathGetAffectedRows(dp);

                    /* load again */

                    state = OCI_DirPathLoad(dp);
                }

                break;
            }
            else if (state == OCI_DPR_COMPLETE)
            {
                /* conversion is complete */

                check_results(con, dp, (gen_conv_error && i == 0 && force) ? (SIZE_ARRAY - NB_ERROR) : SIZE_ARRAY, nb_conv, "converted rows");

                state = OCI_DirPathLoad(dp);

                if (gen_load_error && i == 0)
                {
                    /* on loading error in force mode, check we got the expected errors*/
                    int err_row = OCI_DirPathGetErrorRow(dp);

                    while (err_row != 0)
                    {
                        check_results(con, dp, nb_error == 0 ? 5 : 8, err_row, "erred loaded row index");

                        err_row = OCI_DirPathGetErrorRow(dp);

                        nb_error++;
                    }

                    check_results(con, dp, NB_ERROR, nb_error, "loading errors in force mode");
                }

                check_results(con, dp, (gen_load_error && i == 0) ? nb_conv - NB_ERROR : nb_conv, OCI_DirPathGetAffectedRows(dp), "loaded rows");

                break;
            }
        }
    }

    /* commit load */
    OCI_DirPathFinish(dp);

    /* verify loading results */
    check_results(con, dp, ((gen_conv_error && force) || gen_load_error) ? (NB_TOTAL - NB_ERROR) : NB_TOTAL, OCI_DirPathGetRowCount(dp), "total loaded rows");

    /* check data integrity in database */
    check_data(con, dp, gen_conv_error, gen_load_error, force);

    /* free direct path*/
    OCI_DirPathFree(dp);

    /* drop table */
    drop_table(con);

    printf("SUCCESS\n");
}



