SQLite C言語向けAPI チートシート を ODBC にする

[SQLite C言語向けAPI チートシート](http://qiita.com/katsugeneration/items/26a76f66a1c9cb1710f4) のサンプルプログラムを、ODBC で書き直しました。
カレントディレクトリに myOdbcSQLiteDB というファイルを作って、myTable というテーブルを作ります。

動作環境： Fedora 23, sqlite-devel-3.11.0-3, unixODBC-devel-2.3.4-1, http://www.ch-werner.de/sqliteodbc/ の sqliteodbc-0.9994-1.src.rpm

SQL_NTS とは、 null terminated string で、文字数をあらわに指定する代わりに使うそうです。

~~~
/*
 * odbc_sample.c by kanda.motohiro@gmail.com
 * released under http://qiita.com/terms
 * based on
 * http://qiita.com/katsugeneration/items/26a76f66a1c9cb1710f4 -
 * SQLite C言語向けAPI チートシート
 * see also
 * https://code.msdn.microsoft.com/ODBC-sample-191624ae
 * using sqlite-devel-3.11.0-3, unixODBC-devel-2.3.4-1
 *   and driver at http://www.ch-werner.de/sqliteodbc/
 * cc -g -Wall -Wno-pointer-sign odbc_sample.c  -lodbc
 */
#ifdef WIN32
#include <Windows.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sql.h>
#include <sqlext.h>

#define die() { fprintf(stderr, "err=%d at line %d\n", err, __LINE__); \
        exit(1); } while (0)

#define die2() { printDiag(SQL_HANDLE_STMT, hstmt); die(); } while (0)

// 詳細エラーを表示する。
void
printDiag(SQLSMALLINT type, SQLHANDLE handle)
{
    unsigned char state[6] = "";
    unsigned char msg[SQL_MAX_MESSAGE_LENGTH] = "";
    SQLINTEGER nativeError;

    (void)SQLGetDiagRec(type, handle, 1, state,
                         &nativeError, msg, sizeof(msg), NULL);
    fprintf(stderr, "state=%s msg=%s err=%ld\n", state, msg, nativeError);
}

int
main()
{
    SQLRETURN err;
    SQLHENV henv;
    SQLHDBC hdbc;
    SQLHSTMT hstmt;
    // このファイルにデータベースを作る。
    unsigned char connectionString[] =
#ifdef WIN32
        "Driver=SQLite3 ODBC Driver;Database=myOdbcSQLiteDB;";
#else
        "Driver=SQLITE3;Database=myOdbcSQLiteDB;";
#endif
    SQLINTEGER size;
    int i = 0;

    // データベースのカラムにバインドされる変数
    SQLINTEGER id;
    unsigned char name[32];

    err = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
    if (err != SQL_SUCCESS)
        die();

/*
SQL_SUCCEEDED というマクロがある。
SQL_SUCCESS_WITH_INFO の時も、真を返す。
それが返るのはどういう時かというと、ひとつは、
SQLGetData でバッファが短くて、途中までしかデータが入らなかった
時だそうで、予期しないでそれが返るのはバグ、だろう、ということで、
SQL_SUCCESS だけの比較を使うほうがいい。と、思うよ。
 */
    err = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,
                      (SQLPOINTER) SQL_OV_ODBC3, 0);
    if (err != SQL_SUCCESS)
        die();

    err = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
    if (err != SQL_SUCCESS)
        die();

    // 接続
    err = SQLDriverConnect(hdbc, NULL, connectionString, SQL_NTS,
              NULL, 0, NULL, SQL_DRIVER_NOPROMPT);
    if (err != SQL_SUCCESS) {
        printDiag(SQL_HANDLE_DBC, hdbc);
        die();
    }

    err = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
    if (err != SQL_SUCCESS)
        die();

    // テーブルの作成
    err = SQLExecDirect(hstmt,
                        "CREATE TABLE IF NOT EXISTS myTable "
                        "(id INTEGER PRIMARY KEY AUTOINCREMENT, "
                        "name CHAR(32) NOT NULL)", SQL_NTS);
    if (err != SQL_SUCCESS && err != SQL_NO_DATA)
        die2();
/*
SQL_NO_DATA は、UPDATE 文などで、対象行が無かった時に返るそうだが、
CREATE TABLE でこれが返るのは仕様だろうか。
 */
    // データの追加
    // ステートメントの用意
    err = SQLPrepare(hstmt, "INSERT INTO myTable (name) VALUES (?)", SQL_NTS);
    if (err != SQL_SUCCESS)
        die2();

    // 第二引数は、カラム位置ではなくて、 ? の何番目かです。
    err = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR,
                         SQL_VARCHAR, sizeof(name), 0, name, 0, NULL);
    if (err != SQL_SUCCESS)
        die2();

    for (i = 0; i < 5; i++) {
        snprintf(name, 32, "odbctest%02d", i);
        err = SQLExecute(hstmt);
        if (err != SQL_SUCCESS)
            die2();
    }
    printf("ADD DATA!\n");

    // データの抽出
    err = SQLExecDirect(hstmt, "SELECT * FROM myTable", SQL_NTS);
    if (err != SQL_SUCCESS)
        die();
/*
SQLBindCol は、問い合わせ実行の前に使う。
sqlite3_column_int などは、問い合わせの後に使うので、
SQLGetData の方が、関数の対応としては近い。
sqlite3_column は、結果データのメモリを、関数側が用意して、コール元は確保も解放もする必要がない。
SQLGetData は、メモリはコール元が十分な長さを用意する必要がある。
バッファが小さいと、途中まで入って、SQL_SUCCESS_WITH_INFO が返る。
    err = SQLBindCol(hstmt, 1, SQL_C_SLONG, &id, 0, NULL);
    if (err != SQL_SUCCESS)
        die2();
    err = SQLBindCol(hstmt, 2, SQL_C_CHAR, name, sizeof(name), &size);
    if (err != SQL_SUCCESS)
        die2();
*/
    while (1) {
        err = SQLFetch(hstmt);
        if (err == SQL_NO_DATA_FOUND)
            break;
        else if (err == SQL_ERROR)
            die2();
        err = SQLGetData(hstmt, 1, SQL_C_SLONG, &id, 0, NULL);
        if (err != SQL_SUCCESS)
            die2();
        err = SQLGetData(hstmt, 2, SQL_C_CHAR, name, sizeof(name), &size);
        if (err != SQL_SUCCESS)
            die2();

        printf("%ld %s\n", id, name);
    } // while
    SQLCloseCursor(hstmt);

// ここまでの状態を、 sqlite3 コマンドなどで見るならば、 if 0 でコメントアウト
#if 1
    // データの削除
    // ステートメントの用意
    err = SQLPrepare(hstmt, "DELETE FROM myTable WHERE name == ?", SQL_NTS);
    if (err != SQL_SUCCESS)
        die2();

    err = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR,
                         SQL_VARCHAR, sizeof(name), 0, name, 0, NULL);
    if (err != SQL_SUCCESS)
        die2();

    for (i = 0; i < 5; i++) {
        snprintf(name, 32, "odbctest%02d", i);
        err = SQLExecute(hstmt);
        if (err != SQL_SUCCESS)
            die2();
    }
    printf("REMOVED DATA!\n");

    err = SQLExecDirect(hstmt, "DROP TABLE IF EXISTS myTable", SQL_NTS);
    if (err != SQL_SUCCESS)
        die2();
#endif

    // ステートメントの解放
    SQLFreeHandle(SQL_HANDLE_STMT, hstmt);

    // DBのクローズ
    SQLDisconnect(hdbc);
    SQLFreeHandle(SQL_HANDLE_DBC, hdbc);

    SQLFreeHandle(SQL_HANDLE_ENV, henv);

    return 0;
}
~~~