/*
    colattr.c - test SQLCollAttributes() under OS X

    This file is in the public domain.
*/

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#include <sql.h>
#include <sqlext.h>

/*
    You might want to change these.  You can also call the program as:

        progname [DSN [USER [PASSWORD]]]

    to override these values.  For example, call it as:

        progname myodbc3        -- use the myodbc3 dsn
        progname test abc       -- use the test dsn, with user abc
        progname test abc 0U8I2 -- ditto, with password
*/

static char *dsn = "test";
static char *user = NULL;     /* Use DSN default */
static char *pass = NULL;     /* Use DSN default */

/* Quick hack to provide buffers for query results - adjust as needed */
#define MAX_NAME_LEN 64
#define MAX_COLUMNS 16
#define MAX_ROW_DATA_LEN 1024

/* Max length for ODBC diagnostic messages */
#ifdef SQL_MAX_MESSAGE_LENGTH
#   define MAX_MESSAGE_LENGTH SQL_MAX_MESSAGE_LENGTH
#else
#   define MAX_MESSAGE_LENGTH 1024
#endif

/* Thes are initialized in db_connect() */
static SQLHENV henv;
static SQLHDBC hdbc;
static SQLHSTMT hstmt;


/*
    Convenience macros for error checking:
        env_die         Print diagnostic info from an HENV and exit
        dbc_die         Print diagnostic info from an HDBC and exit
        stmt_die        Print diagnostic info from an HSTMT and exit

        env_warn, dbc_warn, stmt_warn print the info, but do not exit
*/

#define env_die(rc) \
        _error((rc), SQL_HANDLE_ENV, (henv), 1, __FILE__, __LINE__)
#define env_warn(rc) \
        _error((rc), SQL_HANDLE_ENV, (henv), 0, __FILE__, __LINE__)

#define dbc_die(rc) \
        _error((rc), SQL_HANDLE_DBC, (hdbc), 1, __FILE__, __LINE__)
#define dbc_warn(rc) \
        _error((rc), SQL_HANDLE_DBC, (hdbc), 0, __FILE__, __LINE__)

#define stmt_die(rc) \
        _error((rc), SQL_HANDLE_STMT, (hstmt), 1, __FILE__, __LINE__)
#define stmt_warn(rc) \
        _error((rc), SQL_HANDLE_STMT, (hstmt), 0, __FILE__, __LINE__)

static void _error(SQLRETURN rc, SQLSMALLINT htype, SQLHANDLE handle,
        int is_fatal, const char *file, const unsigned int line);

#define RC_ERR(rc) ((rc) < 0)
#define RC_CONTINUE(rc) ((rc) >= 0 && (rc) != SQL_NO_DATA)

void db_connect(void);
void db_disconnect(void);


/*
    Some basic tests
*/

/*
    This function is ripped directly from SQLColumns description in Microsoft's
    ODBC API Reference.
*/

void print_table_attributes()
{
    SQLRETURN rc;
    SQLSMALLINT ncols;

#if 0
    rc = SQLTables(hstmt, NULL, 0, NULL, 0, NULL, 0, NULL, 0);
#else
    rc = SQLColumns(hstmt, NULL, 0, NULL, 0, "t", SQL_NTS, NULL, 0);
#endif
    if (RC_ERR(rc)) dbc_die(rc);

    rc = SQLNumResultCols(hstmt, &ncols);
    if (RC_ERR(rc)) stmt_die(rc);

    {
        int i;
        char buf[64] = "XXX";
        SQLSMALLINT buflen;
        unsigned long int numreturn;

        printf("Table attributes comes in %d columns\n", ncols);
        for (i = 0; i < ncols; ++i) {
            rc = SQLColAttribute(hstmt, i + 1, SQL_DESC_TYPE_NAME,
                    buf, sizeof(buf), &buflen, NULL);
            rc = SQLColAttribute(hstmt, i + 1, SQL_DESC_TYPE,
                    NULL, 0, &buflen, &numreturn);
            if (RC_ERR(rc)) stmt_warn(rc);

            printf("Column %02d: (%s)\t[%lu]\n", i + 1, buf, numreturn);
        }
    }
}

int main(int argc, char **argv)
{
    if (--argc > 0) dsn = *++argv;
    if (--argc > 0) user = *++argv;
    if (--argc > 0) pass = *++argv;

    db_connect();
    print_table_attributes();
    db_disconnect();

    exit(EXIT_SUCCESS);
}


/*
    _error() should be called via the convenience macros.

    If there is any diagnostic info, print it.  If is_fatal is true,
    exit if rc == SQL_ERROR.
*/

static void _error(SQLRETURN rc, SQLSMALLINT htype, SQLHANDLE handle,
        int is_fatal, const char *file, const unsigned int line)
{
    SQLCHAR sql_state[6], message[MAX_MESSAGE_LENGTH + 1];
    SQLINTEGER native_error;
    SQLSMALLINT dummy;
    SQLRETURN new_rc;

    switch (rc) {
        case SQL_SUCCESS:
            fprintf(stderr, "\nSuccess\n");
            break;
        case SQL_ERROR:
        case SQL_SUCCESS_WITH_INFO:
        case SQL_NO_DATA:
        default:
            fprintf(stderr, "\nError at line %s:%u: ", file, line);

            new_rc = SQLGetDiagRec(htype, handle, 1, sql_state, &native_error,
                    message, SQL_MAX_MESSAGE_LENGTH, &dummy);
            if(new_rc == SQL_SUCCESS || new_rc == SQL_SUCCESS_WITH_INFO)
                fprintf(stderr, "[%s:%ld] %s\n",
                        sql_state, native_error, message);
            else
                fprintf(stderr, "Can't get error message (SQLGetDiagRec "
                        "returned rc[%d]; original rc[%d])\n", new_rc, rc);

            break;
    }

    if (is_fatal && rc == SQL_ERROR)
        exit(EXIT_FAILURE);
}


/*
    db_connect() initializes the global henv, hdbc and hstmt variables.
    It uses the global dsn, user, and pass variables.
*/

void db_connect(void)
{
    SQLRETURN rc;

#ifdef ODBCINI
    /* I need this for using iODBC w/ a non-standard odbc.ini location */
    setenv("ODBCINI", ODBCINI, 0);
#endif

    rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
    if (RC_ERR(rc)) env_die(rc);

    rc = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,
            (SQLPOINTER)SQL_OV_ODBC3, 0);
    if (RC_ERR(rc)) env_die(rc);


    printf("Connecting (DSN='%s', USER='%s')\n",
            dsn, user ? user : "<default>");


    rc = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
    if (RC_ERR(rc)) env_die(rc);

    rc = SQLConnect(hdbc, dsn, SQL_NTS, user, SQL_NTS,  pass, SQL_NTS);
    if (RC_ERR(rc)) dbc_die(rc);

    rc = SQLSetConnectAttr(hdbc, SQL_ATTR_AUTOCOMMIT,
            (SQLPOINTER)SQL_AUTOCOMMIT_ON, 0);
    if (RC_ERR(rc)) dbc_die(rc);


    rc = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
    if (RC_ERR(rc)) dbc_die(rc);
}


/*
    db_disconnect() releases all the resources acquired by db_connect()
*/

void db_disconnect(void)
{
    SQLRETURN rc;

    rc = SQLFreeStmt(hstmt, SQL_DROP);
    if (RC_ERR(rc)) dbc_die(rc);

    rc = SQLDisconnect(hdbc);
    if (RC_ERR(rc)) dbc_die(rc);

    rc = SQLFreeConnect(hdbc);
    if (RC_ERR(rc)) dbc_die(rc);

    rc = SQLFreeEnv(henv);
    if (RC_ERR(rc)) env_die(rc);
}

