SQLite Version 3 の DLL の導入と、C/C++アプリケーションからそれを使う方法を解説します。
SQLite本家サイトのダウンロードページから、次の2つのzipファイルを入手します。 ファイル名末尾の数字はリリースバージョンであり、その数字は頻繁に変わります。 3_3 以降の適切なものを選んでダウンロードしてください。
適当な作業フォルダを準備します。
C>mkdir sqlite-sample C>cd sqlite-sample
sqlitedll-3_3_3.zip には以下の2つのファイルが格納されています。 これを適当なunzipツールで作業フォルダに展開します。
Archive: sqlitedll-3_3_3.zip Length Date Time Name -------- ---- ---- ---- 259072 06-02-01 01:24 sqlite3.dll 2055 06-02-01 01:24 sqlite3.def -------- ------- 261127 2 files
sqlite-source-3_3_3.zip にはSQliteのソース一式が格納されています。 そのうち、DLL版を使う場合に必要なソースは sqlite3.h のみです。 適当なunzipツールで sqlite3.h を作業フォルダに展開します。
C>unzip sqlite-source-3_3_3.zip sqlite3.h
Visual C++ の lib コマンドを使って、インポートライブラリ sqlite3.lib を作成します。
C>lib /def:sqlite3.def Microsoft (R) Library Manager Version 6.00.8447 Copyright (C) Microsoft Corp 1992-1998. All rights reserved. LIB : warning LNK4068: /MACHINE の指定がありません; IX86 をデフォルトとします ライブラリ sqlite3.lib とオブジェクト sqlite3.exp を作成中
単純にデータベースファイルを開いて閉じるだけのサンプルプログラムを用意します。
///@file sample1.cpp - how to open and close SQLite3's database. #include <stdio.h> #include <stdlib.h> #include "sqlite3.h" int main(int argc, char** argv) { sqlite3* db; int err; char* db_name = (argc > 1) ? argv[1] : "sample.db"; // open database err = sqlite3_open(db_name, &db); if (err != SQLITE_OK) { // show error and exit. fputs(sqlite3_errmsg(db), stderr); exit(1); } // close database err = sqlite3_close(db); return EXIT_SUCCESS; } // sample1.cpp - end.
このサンプルをコンパイルし、Step3で作成したインポートライブラリとリンクします。
C>cl /MD sample1.cpp sqlite3.lib Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86 Copyright (C) Microsoft Corp 1984-1998. All rights reserved. sample1.cpp Microsoft (R) Incremental Linker Version 6.00.8447 Copyright (C) Microsoft Corp 1992-1998. All rights reserved. /out:sample1.exe sample1.obj sqlite3.lib
出来上がったサンプルを実行してみます。 成功すればカレントフォルダに "sample.db" というファイルが生成されます。
C>sample1 C>dir *.db 2006/02/16 17:58 0 sample.db 1 個のファイル 0 バイト
出来上がったサンプルの実行ファイルを別のフォルダに移動して実行すると、 Windows が "sqlite3.dll"が見つからない" というエラーメッセージボックスを出して、実行を中断します。
SQLiteをWindowsで使用する場合、データベースファイル名のエンコードに注意が必要です。
sqlite3_open() は、API仕様上はUTF-8エンコードしたファイル名を受け取ることになっています。
WindowsNT/XP系では、この仕様通りに動作します。
しかしながらWindows9X系では、UTF-8エンコードすると逆にファイル名が文字化けしてしまいます。
望みのファイル名にするためには、UTF-8エンコードをせず現在ロカールのMBCS文字列を直接渡さなければなりません。
これはSQLite3のWindows版実装のバグです。
データベースにデータを登録し、情報を抽出するプログラムを作成します。
このプログラムは、mbox形式の受信メールボックスファイルを解析し、送信元のIPアドレスを出現頻度の多い順に10個選んで報告します。
その情報は、大量の迷惑メールを送り出す発信元を特定するための手掛かりとして使えます。
この機能を実現するために、データベースを次のように利用します。
ip1: INTEGER | ip2: INTEGER | ip3: INTEGER | ip4: INTEGER | net: TEXT | is_private: TINYINT | header: TEXT |
---|---|---|---|---|---|---|
4分割したIPアドレス値 | IPアドレスのネットワーク部 | プライベートIPなら1、否なら0 | Receivedヘッダ |
CREATE TABLE mail(ip1 INTEGER, ip2 INTEGER, ip3 INTEGER, ip4 INTEGER, net TEXT, is_private TINYINT, header TEXT);
INSERT INTO mail(ip1,ip2,ip3,ip4,net,is_private,header) VALUES(192,168,0,1,'192.168.0.1/24',1,'Received: from unknown(192.168.0.1) by ...');このとき登録処理全体を1つのトランザクションにするため、登録実行ループの前後で次のSQL文を実行します。
BEGIN; 登録実行ループ COMMIT;
SELECT ip1,ip2,ip3,ip4,net,count(*) FROM mail GROUP BY ip1,ip2,ip3,ip4 HAVING is_private=0 ORDER BY count(*) DESC LIMIT 10;
///@file sample2.cpp - how to insert and select data in SQLite3's database. #include <stdio.h> #include <stdlib.h> #include <string.h> #include "sqlite3.h" #include "subfunc.cpp" // IP and FILE I/O functions. /** print result of SQL select. this is called by sqlite3_exec() */ int select_callback(void*, int argc, char** argv, char** columnNames) { // argv[] has a result set of SQL "SELECT ip1,ip2,ip3,ip4,net,count(*) FROM ....;" printf("IP:[%s.%s.%s.%s], NET:[%s], %s:%s\n", argv[0], // ip1 argv[1], // ip2 argv[2], // ip3 argv[3], // ip4 argv[4], // net columnNames[5], argv[5] // count(*) ); return 0; } /** program main. * usage: sample2 [dbfile [mailbox]] * this program works: * - scan received header which contain IP-adderss number from mailbox * - insert them into dbfile * - select top 10 IPs from dbfile * - dbfile default: sample.db * - mailbox default: stdin */ int main(int argc, char** argv) { sqlite3* db; int err; char* db_name = (argc > 1) ? argv[1] : "sample.db"; char* sql; // open input FILE* fin = (argc > 2) ? fopen(argv[2], "rb") : stdin; // use "rb" to input beyond Ctrl-Z. if (!fin) { perror(argv[2]); exit(1); } // open database err = sqlite3_open(db_name, &db); if (err != SQLITE_OK) { // show error and exit. fprintf(stderr, "sqlite3_open: %s\n", sqlite3_errmsg(db)); exit(1); } // create "mail" table err = sqlite3_exec(db, "CREATE TABLE mail(" "ip1 INTEGER, ip2 INTEGER, ip3 INTEGER, ip4 INTEGER, net TEXT," "is_private TINYINT, header TEXT);", NULL, 0, 0); if (err != SQLITE_OK) fprintf(stderr, "sqlite3_exec(CREATE TABLE): %s\n", sqlite3_errmsg(db)); // begin transaction sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL); char buf[1024*8]; while (fgets(buf, sizeof(buf), fin)) { // parse a received header-line from input int c; unsigned ip[4]; if (strncmp(buf, "Received: from ", 15) != 0 || !scan_ip(buf+15, ip)) continue; while ((c = fpeek(fin)) == ' ' || c == '\t') fcatgets(buf, sizeof(buf), fin); // append lines until header end. const char* net = net_address(ip); bool is_private = is_private_ip(ip); // insert IP and header-line into "mail" table sql = sqlite3_mprintf( "INSERT INTO mail(ip1,ip2,ip3,ip4,net,is_private,header) VALUES(%u,%u,%u,%u,'%s',%d,'%q');", ip[0], ip[1], ip[2], ip[3], net, is_private, buf); err = sqlite3_exec(db, sql, NULL, NULL, NULL); if (err != SQLITE_OK) fprintf(stderr, "sqlite3_exec(INSERT INTO mail): %s\n", sqlite3_errmsg(db)); sqlite3_free(sql); }//.endwhile // commit transaction sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL); // select top 10 ip from "mail" table. puts("** Top 10 IPs **"); err = sqlite3_exec(db, "SELECT ip1,ip2,ip3,ip4,net,count(*) FROM mail" " GROUP BY ip1,ip2,ip3,ip4 HAVING is_private=0" " ORDER BY count(*) DESC LIMIT 10;", select_callback, NULL, NULL); if (err != SQLITE_OK) fprintf(stderr, "sqlite3_exec(SELECT): %s\n", sqlite3_errmsg(db)); // close database sqlite3_close(db); // close input fclose(fin); return EXIT_SUCCESS; } // sample2.cpp - end.
より高度なAPIを用いて、先のステップで作成したプログラムを高速化します。
///@file sample3.cpp - how to prepare and step in SQLite3's database. #include <stdio.h> #include <stdlib.h> #include <string.h> #include "sqlite3.h" #include "subfunc.cpp" // IP and FILE I/O functions. /** print result of SQL select using by sqlite3_column_xxx() */ void print_result(sqlite3_stmt* select_sql) { // print result set of SQL "SELECT ip1,ip2,ip3,ip4,net,count(*) FROM ....;" printf("IP:[%d.%d.%d.%d], NET:[%s], %s:%s\n", sqlite3_column_int(select_sql, 0), // ip1 sqlite3_column_int(select_sql, 1), // ip2 sqlite3_column_int(select_sql, 2), // ip3 sqlite3_column_int(select_sql, 3), // ip4 sqlite3_column_text(select_sql, 4), // net sqlite3_column_name(select_sql, 5), // count(*) sqlite3_column_text(select_sql, 5) // count(*) ); } /** program main. * usage: sample3 [dbfile [mailbox]] * this program works: * - scan received header which contain IP-adderss number from mailbox * - insert them into dbfile * - select top 10 IPs from dbfile * - dbfile default: sample.db * - mailbox default: stdin */ int main(int argc, char** argv) { sqlite3* db; int err; char* db_name = (argc > 1) ? argv[1] : "sample.db"; char* sql; // open input FILE* fin = (argc > 2) ? fopen(argv[2], "rb") : stdin; // use "rb" to input beyond Ctrl-Z. if (!fin) { perror(argv[2]); exit(1); } // open database err = sqlite3_open(db_name, &db); if (err != SQLITE_OK) { // show error and exit. fprintf(stderr, "sqlite3_open: %s\n", sqlite3_errmsg(db)); exit(1); } // create "mail" table err = sqlite3_exec(db, "CREATE TABLE mail(" "ip1 INTEGER, ip2 INTEGER, ip3 INTEGER, ip4 INTEGER, net TEXT," "is_private TINYINT, header TEXT);", NULL, 0, 0); if (err != SQLITE_OK) fprintf(stderr, "sqlite3_exec(CREATE TABLE): %s\n", sqlite3_errmsg(db)); // make prepared SQL for mail table sqlite3_stmt* insert_sql; sqlite3_stmt* select_sql; sql = "INSERT INTO mail(ip1,ip2,ip3,ip4,net,is_private,header) VALUES(?,?,?,?,?,?,?);"; sqlite3_prepare(db, sql, strlen(sql), &insert_sql, NULL); const int bind_net = 5; const int bind_is_private = 6; const int bind_header = 7; sql = "SELECT ip1,ip2,ip3,ip4,net,count(*) FROM mail" " GROUP BY ip1,ip2,ip3,ip4 HAVING is_private=0" " ORDER BY count(*) DESC LIMIT 10;"; sqlite3_prepare(db, sql, strlen(sql), &select_sql, NULL); // begin transaction sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL); char buf[1024*8]; while (fgets(buf, sizeof(buf), fin)) { // parse a received header-line from input int c; unsigned ip[4]; if (strncmp(buf, "Received: from ", 15) != 0 || !scan_ip(buf+15, ip)) continue; while ((c = fpeek(fin)) == ' ' || c == '\t') fcatgets(buf, sizeof(buf), fin); // append lines until header end. const char* net = net_address(ip); bool is_private = is_private_ip(ip); // insert IP and header-line into "mail" table sqlite3_reset(insert_sql); sqlite3_bind_int(insert_sql, 1, ip[0]); sqlite3_bind_int(insert_sql, 2, ip[1]); sqlite3_bind_int(insert_sql, 3, ip[2]); sqlite3_bind_int(insert_sql, 4, ip[3]); sqlite3_bind_int(insert_sql, bind_is_private, is_private); sqlite3_bind_text(insert_sql, bind_net, net, strlen(net), SQLITE_STATIC); sqlite3_bind_text(insert_sql, bind_header, buf, strlen(buf), SQLITE_STATIC); err = sqlite3_step(insert_sql); if (err != SQLITE_DONE) fprintf(stderr, "sqlite3_step(INSERT INTO mail): %s\n", sqlite3_errmsg(db)); }//.endwhile // commit transaction sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL); // select top 10 ip from "mail" table. puts("** Top 10 IPs **"); while ((err = sqlite3_step(select_sql)) == SQLITE_ROW) print_result(select_sql); if (err != SQLITE_DONE) fprintf(stderr, "sqlite3_step(SELECT): %s\n", sqlite3_errmsg(db)); // discard prepared SQL sqlite3_finalize(insert_sql); sqlite3_finalize(select_sql); // close database sqlite3_close(db); // close input fclose(fin); return EXIT_SUCCESS; } // sample3.cpp - end.