C言語でSQLiteを使ってみる その2
目次
テスト用のデータベースの内容
テーブル定義:
1 2 3 4 5 6 |
ファイル名:testdb.db テーブル名:testtable カラム名:データ型 id:INTEGER型 (主キー) name:TEXT型 |
テーブルの内容:
1 2 3 4 5 |
id | name --------- 1 | aaa 2 | bbb 3 | ccc |
C言語でSQLiteを用いたプログラム例について
ここでのプログラムは、あくまでプログラム例です。動作確認は自己責任でお願いします。また、エラー処理などはとても手を抜いています。動作したかどうかも終了ステータスだけで判断を行います。Windowsの終了ステータスの確認方法は、コマンドプロンプトで実行ファイルを実行した後に、
1 |
echo %ERRORLEVEL% |
で確認することができます。終了ステータスが0の場合は正常終了、0以外の場合は異常終了とします。
データベースを開いて、閉じるだけのプログラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include "sqlite3.h" int main(void){ sqlite3 *db; //データベースのハンドラ char *filename = "testdb.db"; int rc; //result codes //データベースを開く rc = sqlite3_open(filename, &db); if(rc){ sqlite3_close(db); return 1; } //データベースを閉じる sqlite3_close(db); return 0; } |
簡単な説明
test.dbというデータベースを開いて、閉じるだけのプログラムです。
データベースハンドラは、ポインタ型で作成します。ハンドラとは、私たちが何かを操作するときに必要な参照もしくはオブジェクトのことです。今回はデータベースを操作するため、データベースハンドラといいます。
sqlite3_open()に渡すときは、データベースハンドラをさらにポインタにして、sqlite3_open()の生成するデータベースハンドラを受け取れるようにします。
今回はすぐにデータベースを閉じるため、次にsqlite3_close()で生成されたデータベースハンドラを解放します。
このプログラムを実行するときに、もしtestdb.dbが存在しない場合は、test.dbが作成されます。
SELECT文の実行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
#include <stdio.h> #include "sqlite3.h" int main(void){ sqlite3 *db; char *filename="testdb.db"; int rc; //result codes sqlite3_stmt *stmt; int i; rc = sqlite3_open(filename, &db); if(rc != SQLITE_OK){ return 1; } //プリペアドステートメントを生成 rc = sqlite3_prepare_v2(db,"SELECT id, name FROM testtable", -1, &stmt, 0); if(rc != SQLITE_OK){ printf("ERROR(%d) %s\n",rc, sqlite3_errmsg(db)); sqlite3_close(db); return 2; } //行を読み取る。 rc = sqlite3_step(stmt); while(rc == SQLITE_ROW){ printf("%d %s", sqlite3_column_int(stmt,0), sqlite3_column_text(stmt,1)); printf("\n"); rc = sqlite3_step(stmt); } //プレペアドステートメントの解放 rc = sqlite3_finalize(stmt); if(rc != SQLITE_OK){ printf("ERROR(%d) %s\n",rc,sqlite3_errmsg(db)); } sqlite3_close(db); return 0; } |
簡単な解説
sqlite3_prepare_v2()でプリペアドステートメント(prepared statement)を生成します。その後、sqlite_step()でSQL文の結果から1行読み取ります。sqlite3_prepare_v2()の第1引数は、データベースハンドラです。第2引数にSQL文を取り、第3引数にそのSQL文のバイト数ですが、負の値ならばヌル文字まで読み取ります。第4引数はプレペアドステートメントをポインタで渡します。第5引数は、第2引数と第3引数から読み取らなかったSQL文になります。
読み取った後に、sqlite3_column_int()やsqlite3_column_text()で、その行の値を読み取ります。sqlite3_column_int()やsqlite3_column_text()の2つ目の引数はSQL文結果のカラム名の順番を表しています。
今回の場合、結果はid, nameの順番で並びます。添え字は0から始まるため、0:id, 1:nameとなります。その結果をsqlite3_column_int()やsqlite3_column_text()の2つ目の引数で指定することで結果を取り出します。
行を読み取り終わったら、sqlite3_finalize()で生成されたプリペアドステートメントを解放します。
また、何かエラーが起きた時にsqliteの関数で返却されるコードをsqlite3_errmsg()に渡すことでエラーの内容を表示することができます。
INSERT文の実行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
#include <stdio.h> #include "sqlite3.h" int main(void){ sqlite3 *db; char *filename="testdb.db"; int rc; //result codes sqlite3_stmt *stmt; rc = sqlite3_open(filename, &db); if(rc != SQLITE_OK){ return 1; } //INSERT文の準備 rc = sqlite3_prepare_v2(db,"INSERT INTO testtable(id,name) VALUES " "(4,'ddd')", -1, &stmt, 0); if(rc != SQLITE_OK){ printf("ERROR(%d) %s\n",rc, sqlite3_errmsg(db)); sqlite3_close(db); return 2; } //INSERT文の実行 rc = sqlite3_step(stmt); if(rc != SQLITE_DONE){ printf("ERROR(%d) %s\n",rc,sqlite3_errmsg(db)); } rc = sqlite3_finalize(stmt); if(rc != SQLITE_OK){ printf("ERROR(%d) %s\n",rc,sqlite3_errmsg(db)); } sqlite3_close(db); return 0; } |
簡単な解説
sqlite3_prepare_v2()でINSERT文の準備をしています。INSERT文の実行例として値も直接書き込んでいます。sqlite3_step()で準備したINSERT文を実行します。その際、正常に動作したとき、sqlite3_step()での返却値のコードは、定数SQLITE_DONEになります。
sqlite3_prepare_v2()で使われている文字列の連結の仕方は、C言語に触っていないと少し忘れやすいテクニックだと思います。
INSERT文の実行(プレースホルダとバインド)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
#include <stdio.h> #include "sqlite3.h" int main(void){ sqlite3 *db; char *filename="testdb.db"; int rc; //result codes sqlite3_stmt *stmt; rc = sqlite3_open(filename, &db); if(rc != SQLITE_OK){ return 1; } //INSERT文の準備 rc = sqlite3_prepare_v2(db,"INSERT INTO testtable(id,name) VALUES " "(?,?)", -1, &stmt, 0); if(rc != SQLITE_OK){ printf("ERROR(%d) %s\n",rc, sqlite3_errmsg(db)); sqlite3_close(db); return 2; } //値のバインド sqlite3_bind_int(stmt, 1, 4); sqlite3_bind_text(stmt, 2, "ddd", -1, SQLITE_STATIC); rc = sqlite3_step(stmt); if(rc != SQLITE_DONE){ printf("ERROR(%d) %s\n",rc,sqlite3_errmsg(db)); } //バインドのリセット sqlite3_reset(stmt); //値のバインド sqlite3_bind_int(stmt, 1, 5); sqlite3_bind_text(stmt, 2, "eee", -1, SQLITE_STATIC); rc = sqlite3_step(stmt); if(rc != SQLITE_DONE){ printf("ERROR(%d) %s\n",rc,sqlite3_errmsg(db)); } rc = sqlite3_finalize(stmt); if(rc != SQLITE_OK){ printf("ERROR(%d) %s\n",rc,sqlite3_errmsg(db)); } sqlite3_close(db); return 0; } |
簡単な解説
プレースホルダを用いたINSERT文の例です。「プレースホルダ」とは、文字列の一部を別の文字列に置き換えるもののことです。この例では、「?」がプレースホルダです。もしくは、プレースホルダのことを「バインド変数」ともいいます。
また、プレースホルダに値を入れることを「バインド(bind)する」といいます。sqlite3_bind_int()やsqlite3_bind_text()で値をバインドします。第1引数は、sqlite3_prepare_v2()で生成したプレペアドステートメントです。第2引数はプレースホルダの位置で最初に現れたものが1となります。その後に現れるプレースホルダが、2,3…と続きます。第3引数が値になります。
sqlite3_bind_text()の第4引数はヌル文字のまでのバイト数で、ここが負の値の場合、ヌル文字まで自動で値をbindします。
sqlite3_bind_text()の第5引数はバインドした値のデストラクタになります。型はvoid(*)(void*)で、この引数にはバインドしたデータのポインタが入るため、これを利用してデータを解放する。また、バインドする値が決して変わらない場合は、定数SQLITE_STATICを使い、バインドする値が変化する場合は、定数SQLITE_TRANSIENTを使う。例えば、値をバインドするときに変数をバッファとして使いまわす場合などは、SQLITE_TRANSIENTを使う。
INSERT文の実行(プレースホルダとバインド)ループの形
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
#include <stdio.h> #include "sqlite3.h" int main(void){ sqlite3 *db; char *filename="testdb.db"; int rc; //result codes sqlite3_stmt *stmt; int db_id[2] = {4, 5}; char *db_name[2] = {"ddd", "eee"}; int i; rc = sqlite3_open(filename, &db); if(rc != SQLITE_OK){ return 1; } rc = sqlite3_prepare_v2(db,"INSERT INTO testtable(id,name) VALUES " "(?,?)", -1, &stmt, 0); if(rc != SQLITE_OK){ printf("ERROR(%d) %s\n",rc, sqlite3_errmsg(db)); sqlite3_close(db); return 2; } //ループの形の例 for (i = 0; i < 2; i++) { sqlite3_bind_int(stmt, 1, db_id[i]); sqlite3_bind_text(stmt, 2, db_name[i], sizeof(db_name[i]), SQLITE_STATIC); rc = sqlite3_step(stmt); if(rc != SQLITE_DONE){ printf("ERROR(%d) %s\n",rc,sqlite3_errmsg(db)); } sqlite3_reset(stmt); } rc = sqlite3_finalize(stmt); if(rc != SQLITE_OK){ printf("ERROR(%d) %s\n",rc,sqlite3_errmsg(db)); } sqlite3_close(db); return 0; } |
簡単な解説
上のINSERT文の例をループの形にしてみただけの例です。
DELETE文の実行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#include <stdio.h> #include "sqlite3.h" int main(void){ sqlite3 *db; char *filename="testdb.db"; int rc; //result codes sqlite3_stmt *stmt; rc = sqlite3_open(filename, &db); if(rc != SQLITE_OK){ return 1; } //DELETE文の準備 rc = sqlite3_prepare_v2(db,"DELETE FROM testtable where id=2", -1, &stmt, 0); if(rc != SQLITE_OK){ printf("ERROR(%d) %s\n",rc, sqlite3_errmsg(db)); sqlite3_close(db); return 2; } //DELETE文の実行 rc = sqlite3_step(stmt); if(rc != SQLITE_DONE){ printf("ERROR(%d) %s\n",rc,sqlite3_errmsg(db)); } rc = sqlite3_finalize(stmt); if(rc != SQLITE_OK){ printf("ERROR(%d) %s\n",rc,sqlite3_errmsg(db)); } sqlite3_close(db); return 0; } |
簡単な解説
DELETE文でidが2のデータを削除してます。前回のINSERT文でのプレースホルダを活用すれば、プログラムはより良くなります。
UPDATE文の実行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#include <stdio.h> #include "sqlite3.h" int main(void){ sqlite3 *db; char *filename="testdb.db"; int rc; //result codes sqlite3_stmt *stmt; rc = sqlite3_open(filename, &db); if(rc != SQLITE_OK){ return 1; } //UPDATE文の準備 rc = sqlite3_prepare_v2(db,"UPDATE testtable SET name='xxx' where id=1", -1, &stmt, 0); if(rc != SQLITE_OK){ printf("ERROR(%d) %s\n",rc, sqlite3_errmsg(db)); sqlite3_close(db); return 2; } //UPDATE文の実行 rc = sqlite3_step(stmt); if(rc != SQLITE_DONE){ printf("ERROR(%d) %s\n",rc,sqlite3_errmsg(db)); } rc = sqlite3_finalize(stmt); if(rc != SQLITE_OK){ printf("ERROR(%d) %s\n",rc,sqlite3_errmsg(db)); } sqlite3_close(db); return 0; } |
簡単な解説
こちらもDELETE文と同じよう形で、idが1のデータで、nameを「xxx」に書き換えています。このプログラムも前回のINSERT文でのプレースホルダを活用すれば、より良いプログラムになります。
CREATE TABLE文の実行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
#include <stdio.h> #include "sqlite3.h" int callback(void *NotUsed, int result_num, char **result_value, char **ColName){ int i; int *notused; notused = NotUsed; for(i = 0; i < result_num; i++){ printf("%s = %s\n", ColName[i], result_value[i] ? result_value[i] : "NULL"); } printf("\n"); return 0; } int main(void){ sqlite3 *db; char *filename="testdb.db"; int rc; //result codes sqlite3_stmt *stmt; rc = sqlite3_open(filename, &db); if(rc != SQLITE_OK){ return 1; } //CREATE TABLE文の実行 rc = sqlite3_exec(db, "CREATE TABLE testtable2 (id2 integer primary key,name2 text)", NULL, NULL, NULL); if(rc != SQLITE_OK){ printf("ERROR(%d) %s\n",rc, sqlite3_errmsg(db)); sqlite3_close(db); return 2; } //データベース全体のテーブルの表示 rc = sqlite3_exec(db, "SELECT * from sqlite_master where type='table'", callback, NULL, NULL); if(rc != SQLITE_OK){ printf("ERROR(%d) %s\n",rc, sqlite3_errmsg(db)); sqlite3_close(db); return 2; } sqlite3_close(db); return 0; } |
実行結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
type = table name = testtable tbl_name = testtable rootpage = 2 sql = CREATE TABLE [testtable] ( [id] INTEGER, [name] TEXT, PRIMARY KEY(id) ) type = table name = testtable2 tbl_name = testtable2 rootpage = 3 sql = CREATE TABLE testtable2 (id2 integer primary key,name2 text) |
簡単な解説
CREATE TABLE文の実行例です。CREATE TABLE文でテーブルを作成した後に、テーブルが作成されたか確認をするために、sqliteのデータベースの作成時にできるsqlite_masterというテーブルを使って、データベース全体のテーブルを確認しています。
sqlite3_exec()は、「sqlite3_prepare_v2()→sqlite3_step()→sqlite3_finalize()」までの一連の流れを行ってくれるラッパー関数です。ラッパーとは、ラップ(wrap、包む)という単語からくる言葉で、sqlite3_exec()は「sqlite3_prepare_v2()→sqlite3_step()→sqlite3_finalize()」の流れをラップして(包んで)います。
sqlite3_exec()の第1引数はデータベースハンドラで、第2引数はSQL文です。第3引数は、ステートメントを読み取るごとに呼び出されるコールバック関数で、第4引数はそのコールバック関数に渡すことができる引数になります。第5引数で、エラーメッセージを受け取ることができます。
CREATE TABLE文などのような一回で終わるようなSQL文はsqlite3_exec()で実行すると非常に簡単で、また、SELECT文のようなSQL文でデータを読みだすときもコールバック関数を用いることで、非常に簡単にデータを読み出すことができます。
sqlite3_exec()のコールバック関数の引数は、第1引数はsqlite3_exec()の第4引数で、第2引数はSQL文の結果の行数で、第3引数はデータの値(の配列)で、第4引数がカラム名(の配列)となります。
TRANSACTIONの実行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#include <stdio.h> #include "sqlite3.h" int main(void){ sqlite3 *db; char *filename="testdb.db"; int rc,rc1,rc2,rc3; //result codes rc = sqlite3_open(filename, &db); if(rc != SQLITE_OK){ return 1; } //TRANSACTIONの開始 sqlite3_exec(db,"BEGIN TRANSACTION",0,0,0); rc1 = sqlite3_exec(db,"INSERT INTO testtable(id,name) VALUES (4,'ddd')",0,0,0); rc2 = sqlite3_exec(db,"INSERT INTO testtable(id,name) VALUES (5,'eee')",0,0,0); rc3 = sqlite3_exec(db,"INSERT INTO testtable(id,name) VALUES (6,'fff')",0,0,0); if((rc1 == SQLITE_OK) && (rc2 == SQLITE_OK) && (rc3 == SQLITE_OK)){ //正常処理 sqlite3_exec(db,"COMMIT",0,0,0); }else{ //異常処理 sqlite3_exec(db,"ROLLBACK",0,0,0); } sqlite3_close(db); return 0; } |
簡単な解説
データベースでは、処理の一連の流れを処理する際にトランザクションを使います。トランザクションは、一連の流れのどこかが失敗してしまったら、トランザクションを始めた状態にデータベースの状態を戻すことができます。トランザクションを行えば、例えば銀行で、Aさんの口座から1000円ひかれて、Bさんの口座に1000円が入っていないというような状況を防ぐことができます。
SQLiteのトランザクションは「BEGIN TRANSACTION」でトランザクションを開始して、「COMMIT」で処理の完了、「ROLLBACK」でトランザクション前の状態に戻すことができます。
参考
SQLite Documentation(https://www.sqlite.org/docs.html)