/**********************************************************************
* FILENAME :        util.c
*
* DESCRIPTION :     Various useful functions
*
*                   rtrim  - right trim string
*                   getStr - get string from user
*                   getInt - get integer value from user
*                   extract_error - get error for handle from driver
*                   padOut - pad out string to given length
*                   itoa   - create string of integer
*                   getDataSources - get list of Data Sources
*                   selectDSN      - select data source
*                   getDescRecCount - get number of records in descriptor
*                   dumpDescriptorRec - uses SQLGetDescRec and output info
*                                       for descriptor record
*                   dumpDescriptorHeaderFields - outouts descriptor
*                                                header fields
*                   dumpDescriptorRecordFields - outputs descriptor
*                                                record fields
*                   dumpDescriptors - dumps out descriptors in use on handle
*                   hasBookmarkRecord - returns true/false to indicate
*                                       whether handle has bookmark rec (0)
*
* ODBC USAGE :
*                   See code.
*/

#define CHECK_ERROR(e, s, h, t) ({\
            if (e!=SQL_SUCCESS && e != SQL_SUCCESS_WITH_INFO) {extract_error(s, h, t); goto exit;} \
})

#define MAXDSNS 10
#define MAXDSNLEN 100

char* rtrim(char* string, char junk)
{
    char* original = string + strlen(string);
    while(*--original == junk);
        *(original + 1) = '\0';
    return string;
}

// get string from user.
char getStr (char *label, char *retStr, int len, char confirm) {

    char reply[3];
    char *nl=0;

    strcpy (reply, "Y");

    if (strlen(label) > 0) {
        printf ("%s : ", label);
    }

    fgets(retStr, len, stdin);
    if ( (nl = strchr (retStr, '\n')) != NULL) {*nl='\0';}

    if (confirm == 'Y') {
        printf ("Confirm Y/N? : ");
        fgets(reply, 3, stdin);
    }

    return reply[0];
}

// gets integer value from user. confirm ignored if number equals quitVal
char getInt (char *label, int *retInt, char confirm, int quitVal) {

    char reply[3];
    char buff[256];

    if (strlen(label)>0) {
        printf ("%s : ", label);
    }

    fgets(buff, sizeof(buff), stdin);
    *retInt=atoi(buff);
    if (confirm=='Y' && *retInt!=quitVal) {
        printf ("Confirm Y/N? : ");
        fgets(reply, 3, stdin);
    }

    return reply[0];
}

void extract_error(char *fn, SQLHANDLE handle, SQLSMALLINT type)
{
    SQLINTEGER i = 0;
    SQLINTEGER NativeError;
    SQLCHAR SQLState[ 7 ];
    SQLCHAR MessageText[256];
    SQLSMALLINT TextLength;
    SQLRETURN ret;

    fprintf(stderr, "\nThe driver reported the following error %s\n", fn);
    do
    {
        ret = SQLGetDiagRec(type, handle, ++i, SQLState, &NativeError,
                            MessageText, sizeof(MessageText), &TextLength);
        if (SQL_SUCCEEDED(ret)) {
            printf("%s:%ld:%ld:%s\n",
                        SQLState, (long) i, (long) NativeError, MessageText);
        }
    }
    while( ret == SQL_SUCCESS );
}

void padOut (char* data, char* padding, int max) {

    memset (padding, ' ', max);
    padding[max]='\0';

    //printf ("Data    '%s', Len %i\n", data, (int)strlen(data));
    //printf ("Padding '%s', Len %i\n", padding, (int)strlen(padding));
    //printf ("Max     %i\n", max);

    if (strlen(data)<strlen(padding)){
        padding[strlen(padding)-strlen(data)]='\0';
    } else {
        padding[0]='\0';
    }
    return;
}

//implemenation of itoa

char* itoa(int n) {
    char* ret = NULL;
    int numChars = 0;

    // Determine if integer is negative
    int isNegative = 0;

    if (n < 0) {
        n = -n;
        isNegative = 1;
        numChars++;
    }

    // Count how much space we will need for the string
    int temp = n;
    do {
        numChars++;
        temp /= 10;
    } while ( temp );

    // Allocate space for the string (1 for negative sign, 1 for each digit,
    // and 1 for null terminator)
    ret = malloc (numChars + 1);
    ret[numChars] = 0;

    // Add the negative sign if needed
    if (isNegative) ret[0] = '-';

    // Copy digits to string in reverse order
    int i = numChars - 1;
    do {
        ret[i--] = n%10 + '0';
        n /= 10;
    } while (n);

    return ret;
}

// Example of passing 2D array ...

int getDataSources (SQLHANDLE env, int maxNo, int maxLen,
                    char dsns[maxNo][maxLen]) {

	SQLRETURN retcode;
	SQLCHAR dsnName[256];
	SQLSMALLINT dsnNameLenReturned;
	SQLCHAR driverDesc[256];
	SQLSMALLINT driverDescLenReturned;
	SQLUSMALLINT direction;
    int count=0;

	direction = SQL_FETCH_FIRST;
	while (SQL_SUCCEEDED(retcode=SQLDataSources(env, direction, dsnName,
                                                sizeof(dsnName),
                                                &dsnNameLenReturned,
                                                driverDesc,
                                                sizeof(driverDesc),
                                                &driverDescLenReturned))) {
	    direction = SQL_FETCH_NEXT;
        strcpy (&dsns[count][0], dsnName);
        count++;
	}

    return count;
}

int selectDSN (SQLHANDLE henv, char *dsn, char *prompt) {

    char dsns[MAXDSNS][MAXDSNLEN];
    int count, i;
    count = getDataSources(henv, MAXDSNS, MAXDSNLEN, dsns);

    printf ("%s\n", prompt);
    printf ("0 - Quit\n");
    for (i=0;i<count;i++) {
        printf ("%i - %s\n", i+1, dsns[i]);
    }

    getInt ("DSN ? ", &i, 'N', 0);
    if (i==0)
        return SQL_ERROR;
    else {
        strcpy (dsn, &dsns[i-1][0]);
        return SQL_SUCCESS;
    }
}

//
// get number of column/param records in descriptor
// count does not include bookmark record ever
//
SQLSMALLINT getDescRecCount (SQLHDESC descriptor) {

    SQLRETURN retcode;
    SQLSMALLINT descCount=0;

    // get number of fields in the descriptor
    retcode = SQLGetDescField(descriptor, 0, SQL_DESC_COUNT, &descCount, 0, 0);
    if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
        extract_error("SQLGetDescField (COUNT)",
                                        descriptor, SQL_HANDLE_DESC);
        descCount=0;
    }
    return descCount;
}

//
// Dump various settings or values of multiple fields of a descriptor record
// using ODBC function SQLGetDescRec ()
//
void dumpDescriptorRec (char *comment,
                        SQLHDESC descriptor,
                        int incBookmarkRec) {

    int i;
    SQLRETURN       retcode;

    SQLCHAR         Name[255];
    SQLSMALLINT     BufferLength=255;
    SQLSMALLINT     StringLength;
    SQLSMALLINT     Type;
    SQLSMALLINT     SubType;
    SQLLEN          Length;
    SQLSMALLINT     Precision;
    SQLSMALLINT     Scale;
    SQLSMALLINT     Nullable;

    SQLSMALLINT     RecNumber;
    SQLSMALLINT     FieldIdentifier;

    SQLSMALLINT     descCount=0;
    SQLINTEGER      Len;

    printf ("\n---\n%s\n---\n", comment);
    descCount = getDescRecCount(descriptor);
    printf ("%i Records\n", (int) descCount);

    // Descriptor records are numbered from 0,
    // with record number 0 being the bookmark record.
    if (incBookmarkRec) {
        i=-1;
    } else {
        i=0;
    }

    for (;i<descCount;i++) {
        retcode = SQLGetDescRec(descriptor, i+1,
                                Name, BufferLength,
                                &StringLength, &Type,
                                &SubType, &Length,
                                &Precision, &Scale,
                                &Nullable);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            if (retcode==SQL_NO_DATA) {
                printf ("\nNo %s records !\n", comment);
            } else {
                 extract_error("SQLGetDescRec (hdbc)",
                                        descriptor, SQL_HANDLE_DESC);
                 break;
            }
        } else {
            printf ("\nName %s, ",      Name);
            printf ("Type %i, ",        (int)Type);
            printf ("SubType %i, ",     (int)SubType);
            printf ("Length %i, ",      (int)Length);
            printf ("Precision %i, ",   (int)Precision);
            printf (" Scale %i, ",      (int)Scale);
            printf ("Nullable %i\n",    (int)Nullable);
        }
    }

    return;
}

//
// Dumps out descriptor header record fields using ODBC
// function SQLGetDescField ()
//
void dumpDescriptorHeaderFields (SQLHDESC descriptor) {

    SQLRETURN       retcode;

    //
    // Header fields
    //
    SQLSMALLINT     descAllocType=0;
    SQLULEN         descArraySize=0;
    SQLUSMALLINT   *descArrayStatusPtr;
    SQLLEN         *descBindOffsetPtr;
    SQLINTEGER      descBindType=0;
    SQLSMALLINT     descCount=0;
    SQLULEN        *descRowsProcessesPtr;

    printf ("\n  Descriptor Header Fields");
    printf ("\n  ------------------------\n");
    retcode = SQLGetDescField(descriptor, 0, SQL_DESC_ALLOC_TYPE,
                                             &descAllocType, 0, 0);
    if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
        extract_error("SQLGetDescField (ALLOC_TYPE)",
                                descriptor, SQL_HANDLE_DESC);
        return;
    } else {
        printf (" SQL_DESC_ALLOC_TYPE : %i\n", (int) descAllocType);
    }

    retcode = SQLGetDescField(descriptor, 0, SQL_DESC_ARRAY_SIZE,
                                             &descArraySize, 0, 0);
    if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
        extract_error("SQLGetDescField (ARRAY_SIZE)",
                                descriptor, SQL_HANDLE_DESC);
        return;
    } else {
        printf (" SQL_DESC_ARRAY_SIZE : %i\n", (int) descArraySize);
    }

    retcode = SQLGetDescField(descriptor, 0, SQL_DESC_ARRAY_STATUS_PTR ,
                                             &descArrayStatusPtr, 0, 0);
    if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
        extract_error("SQLGetDescField (ARRAY_STATUS_PTR )",
                                            descriptor, SQL_HANDLE_DESC);
        return;
    } else {
        printf (" SQL_DESC_ARRAY_STATUS_PTR  : %p\n", descArrayStatusPtr);
    }

    retcode = SQLGetDescField(descriptor, 0, SQL_DESC_BIND_OFFSET_PTR ,
                                             &descBindOffsetPtr, 0, 0);
    if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
        extract_error("SQLGetDescField (BIND_OFFSET_PTR )",
                                            descriptor, SQL_HANDLE_DESC);
        return;
    } else {
        printf (" SQL_DESC_BIND_OFFSET_PTR  : %p\n", descBindOffsetPtr);
    }

    retcode = SQLGetDescField(descriptor, 0, SQL_DESC_BIND_TYPE ,
                                             &descBindType, 0, 0);
    if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
        extract_error("SQLGetDescField (BIND_TYPE )",
                                            descriptor, SQL_HANDLE_DESC);
        return;
    } else {
        printf (" SQL_DESC_BIND_TYPE  : %i\n", descBindType);
    }

    retcode = SQLGetDescField(descriptor, 0, SQL_DESC_COUNT ,
                                             &descCount, 0, 0);
    if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
        extract_error("SQLGetDescField (COUNT )",
                                            descriptor, SQL_HANDLE_DESC);
        return;
    } else {
        printf (" SQL_DESC_COUNT  : %i\n", descCount);
    }

    retcode = SQLGetDescField(descriptor, 0, SQL_DESC_ROWS_PROCESSED_PTR ,
                                             &descRowsProcessesPtr, 0, 0);
    if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
        extract_error("SQLGetDescField (ROWS_PROCESSED_PTR )",
                                            descriptor, SQL_HANDLE_DESC);
        return;
    } else {
        printf (" SQL_DESC_ROWS_PROCESSED_PTR  : %p\n", descRowsProcessesPtr);
    }

    return;
}

//
// Dump out descriptor records fields using ODBC function SQLGetDescField ()
//
void dumpDescriptorRecordFields (SQLHDESC descriptor, int incBookmarkRec) {

    SQLINTEGER      descAutoUniqueValue;
    SQLCHAR         descBaseColumnName[255];
    SQLCHAR         descBaseTableName[255];
    SQLINTEGER      descCaseSensitive;
    SQLCHAR         descCatalogName[255];
    SQLSMALLINT     descConsiseType;
    SQLPOINTER      descDataPtr;
    SQLSMALLINT     descDatetimeIntervalCode;
    SQLINTEGER      descDatetimeIntervalPrecision;
    SQLLEN          descDisplaySize;
    SQLSMALLINT     descFixedPreCScale;
    SQLLEN         *descIndicatorPtr;
    SQLCHAR         descLabel[255];
    SQLULEN         descLength;
    SQLCHAR         descLiteralPrefix[255];
    SQLCHAR         descLiteralSuffix[255];
    SQLCHAR         descLocalTypeName[255];
    SQLCHAR         descName[255];
    SQLSMALLINT     descNullable;
    SQLINTEGER      descNumPrecRadix;
    SQLLEN          descOctetLength;
    SQLLEN         *descOctetLengthPtr;
    SQLSMALLINT     descParameterType;
    SQLSMALLINT     descPrecision;
    SQLSMALLINT     descRowver;
    SQLSMALLINT     descScale;
    SQLCHAR         descSchemaName[255];
    SQLSMALLINT     descSearchable;
    SQLCHAR         descTableName[255];
    SQLSMALLINT     descType=0;
    SQLCHAR         descTypeName[255];
    SQLSMALLINT     descUnnamed;
    SQLSMALLINT     descUnsigned;
    SQLSMALLINT     descUpdatable;
    SQLINTEGER      Len;

    SQLRETURN retcode;
    SQLSMALLINT descCount;

    descCount=getDescRecCount (descriptor);

    printf ("\n  Descriptor Record Fields");
    printf ("\n  ------------------------\n");

    int i;
    if (incBookmarkRec) {
        i=-1;
    } else {
        i=0;
    }
    for (;i<descCount;i++) {
        printf ("\n  Record %i ", i+1);
        if (i==-1) printf ("(BOOKMARK RECORD)");
        printf ("\n");
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_AUTO_UNIQUE_VALUE ,
                                  &descAutoUniqueValue, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (AUTO_UNIQUE_VALUE)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_AUTO_UNIQUE_VALUE  : %i\n", descAutoUniqueValue);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_BASE_COLUMN_NAME ,
                                  descBaseColumnName, 255, &Len);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (BASE_COLUMN_NAME)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_BASE_COLUMN_NAME  : '%s'\n",
                                                descBaseColumnName);
        }

        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_BASE_TABLE_NAME ,
                                  descBaseTableName, 255, &Len);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (BASE_TABLE_NAME)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_BASE_TABLE_NAME  : '%s'\n", descBaseTableName);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_CASE_SENSITIVE ,
                                  &descCaseSensitive, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (CASE_SENSITIVE)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_CASE_SENSITIVE  : %i\n", descCaseSensitive);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_CATALOG_NAME ,
                                  &descCatalogName, 255, &Len);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (CATALOG_NAME)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_CATALOG_NAME  : '%s'\n", descCatalogName);
        }

        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_CONCISE_TYPE ,
                                  &descConsiseType, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (CONCISE_TYPE)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_CONCISE_TYPE  : %i\n", descConsiseType);
        }

        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_DATA_PTR ,
                                  &descDataPtr, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (DATA_PTR)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_DATA_PTR  : %p\n", descDataPtr);
        }

        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_DATETIME_INTERVAL_CODE ,
                                  &descDatetimeIntervalCode, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (DATETIME_INTERVAL_CODE)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_DATETIME_INTERVAL_CODE  : %i\n",
                                                (int)descDatetimeIntervalCode);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_DATETIME_INTERVAL_PRECISION ,
                                  &descDatetimeIntervalPrecision, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (DATETIME_INTERVAL_PRECISION)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_DATETIME_INTERVAL_PRECISION  : %i\n",
                                                descDatetimeIntervalPrecision);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_DISPLAY_SIZE ,
                                  &descDisplaySize, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (DISPLAY_SIZE)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_DISPLAY_SIZE  : %i\n", (int)descDisplaySize);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_FIXED_PREC_SCALE ,
                                  &descFixedPreCScale, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (FIXED_PREC_SCALE)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_FIXED_PREC_SCALE  : %i\n",
                                                (int)descFixedPreCScale);
        }

        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_INDICATOR_PTR ,
                                  &descIndicatorPtr, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (INDICATOR_PTR)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_INDICATOR_PTR  : %p\n", descIndicatorPtr);
        }

        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_LABEL ,
                                  descLabel, 255, &Len);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (LABEL )",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_LABEL  : '%s'\n", descLabel);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_LENGTH ,
                                  &descLength, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (LENGTH)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_LENGTH  : %i\n", (unsigned int)descLength);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_LITERAL_PREFIX ,
                                  descLiteralPrefix, 255, &Len);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (LITERAL_PREFIX)",
                                                descriptor, SQL_HANDLE_DESC);

        } else {
            printf (" SQL_DESC_LITERAL_PREFIX  : '%s'\n", descLiteralPrefix);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_LITERAL_SUFFIX ,
                                  descLiteralSuffix, 255, &Len);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (LITERAL_SUFFIX)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_LITERAL_SUFFIX  : '%s'\n", descLiteralSuffix);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_LOCAL_TYPE_NAME ,
                                  descLocalTypeName, 255, &Len);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (LOCAL_TYPE_NAME)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_LOCAL_TYPE_NAME  : '%s'\n", descLocalTypeName);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_NAME ,
                                  descName, 255, &Len);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (NAME )",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_NAME  : '%s'\n", descName);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_NULLABLE ,
                                  &descNullable, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (NULLABLE)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_NULLABLE  : %i\n", (int)descNullable);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_NUM_PREC_RADIX ,
                                  &descNumPrecRadix, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (NUM_PREC_RADIX)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_NUM_PREC_RADIX  : %i\n", (int)descNumPrecRadix);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_OCTET_LENGTH ,
                                  &descOctetLength, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (OCTET_LENGTH)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_OCTET_LENGTH  : %i\n", (int)descOctetLength);
        }

        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_OCTET_LENGTH_PTR ,
                                  &descOctetLengthPtr, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (OCTET_LENGTH_PTR)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_OCTET_LENGTH_PTR  : %p\n", descOctetLengthPtr);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_PARAMETER_TYPE ,
                                  &descParameterType, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (PARAMETER_TYPE)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_PARAMETER_TYPE  : %i\n", (int)descParameterType);
        }

        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_PRECISION ,
                                  &descPrecision, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (PRECISION)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_PRECISION  : %i\n", (int)descPrecision);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_ROWVER ,
                                  &descRowver, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (ROWVER)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_ROWVER  : %i\n", (int)descRowver);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_SCALE ,
                                  &descScale, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (SCALE)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_SCALE  : %i\n", (int)descScale);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_SCHEMA_NAME ,
                                  &descSchemaName, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (SCHEMA_NAME)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_SCHEMA_NAME  : '%s'\n", descSchemaName);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_SEARCHABLE ,
                                  &descSearchable, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (SEARCHABLE)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_SEARCHABLE  : %i\n", (int)descSearchable);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_TYPE ,
                                  &descType, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (TYPE )",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_TYPE  : %i\n", (int)descType);
        }

        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_TYPE_NAME ,
                                  descTypeName, 255, &Len);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (TYPE_NAME )",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_TYPE_NAME  : '%s'\n", descTypeName);
        }

        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_UNNAMED ,
                                  &descUnnamed, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (UNNAMED)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_UNNAMED  : %i\n", descUnnamed);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_UNSIGNED ,
                                  &descUnsigned, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (UNSIGNED)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_UNSIGNED  : %i\n", descUnsigned);
        }
         retcode = SQLGetDescField(descriptor, i+1,
                                   SQL_DESC_UPDATABLE ,
                                   &descUpdatable, 0, 0);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (UPDATABLE)",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_UPDATABLE  : %i\n", descUpdatable);
        }
        retcode = SQLGetDescField(descriptor, i+1,
                                  SQL_DESC_TABLE_NAME ,
                                  descTableName, 255, &Len);
        if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
            extract_error("SQLGetDescField (TABLE_NAME )",
                                                descriptor, SQL_HANDLE_DESC);
        } else {
            printf (" SQL_DESC_TABLE_NAME  : '%s'\n", descTableName);
        }

    }

    return;
}

//
// dump descriptors
//
void dumpDescriptors (SQLCHAR *comment,         // display heading/comment
                      SQLHSTMT hstmt,           // statement handle
                      USHORT header,            // display header record
                      USHORT records,           // display col/parm records
                      int incBookmarkRec) {     // include bookmark record

    SQLRETURN retcode;
    SQLHDESC  hArd, hIrd, hApd, hIpd;           // descriptors

    // Get ARD
    retcode = SQLGetStmtAttr(hstmt, SQL_ATTR_APP_ROW_DESC, &hArd, 0, NULL);
    CHECK_ERROR(retcode, "SQLGetStmtAttr(SQL_HANDLE_STMT)",
                hstmt, SQL_HANDLE_STMT);

    // Get IRD
    retcode = SQLGetStmtAttr(hstmt, SQL_ATTR_IMP_ROW_DESC, &hIrd, 0, NULL);
    CHECK_ERROR(retcode, "SQLExecDirect(SQL_HANDLE_STMT)",
                hstmt, SQL_HANDLE_STMT);

    // Get APD
    retcode = SQLGetStmtAttr(hstmt, SQL_ATTR_APP_PARAM_DESC, &hApd, 0, NULL);
    CHECK_ERROR(retcode, "SQLExecDirect(SQL_HANDLE_STMT)",
                hstmt, SQL_HANDLE_STMT);

    // Get IPD
    retcode = SQLGetStmtAttr(hstmt, SQL_ATTR_IMP_PARAM_DESC, &hIpd, 0, NULL);
    CHECK_ERROR(retcode, "SQLExecDirect(SQL_HANDLE_STMT)",
                hstmt, SQL_HANDLE_STMT);

    printf ("\n---\n%s\n---\n", comment);
    dumpDescriptorRec ("ARD", hArd, incBookmarkRec);
    if (header=='Y') {
        dumpDescriptorHeaderFields (hArd);
    }
    if (records=='Y') {
        dumpDescriptorRecordFields (hArd, incBookmarkRec);
    }

    dumpDescriptorRec ("IRD", hIrd, incBookmarkRec);
    if (header=='Y') {
        dumpDescriptorHeaderFields (hIrd);
    }
    if (records='Y') {
        dumpDescriptorRecordFields (hIrd, incBookmarkRec);
    }

    dumpDescriptorRec ("APD", hApd, incBookmarkRec);
    if (header=='Y') {
        dumpDescriptorHeaderFields (hApd);
    }
    if (records='Y') {
        dumpDescriptorRecordFields (hApd, incBookmarkRec);
    }

    dumpDescriptorRec ("IPD", hIpd, incBookmarkRec);
    if (header=='Y') {
        dumpDescriptorHeaderFields (hIpd);
    }
    if (records='Y') {
        dumpDescriptorRecordFields (hIpd, incBookmarkRec);
    }

exit:

    return;
}

//
// returns true/false if descriptor has/hasn't bookmark record
//
int hasBookmarkRecord (SQLHANDLE handle) {

    SQLRETURN retcode;
    SQLULEN   bookmark;

    retcode = SQLGetStmtAttr(handle, SQL_ATTR_USE_BOOKMARKS,
                             &bookmark, 0, 0);
    if ( (retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) ) {
        extract_error("SQLGetStmtAttr (SQL_ATTR_USE_BOOKMARKS)",
                      handle, SQL_HANDLE_ENV);
        return 0;
    }
    if (bookmark==SQL_UB_OFF)
        return 0;
    else
        return 1;
}

Further information