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;
}