LoginSignup
2
2

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-11-20

SQLite C言語向けAPI チートシート のサンプルプログラムを、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;
}
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2