Visual C++ で SQLite を使う
まとめ
意外と情報が少なくて苦労した.結論としては,SQLite 公式のソースコードを落としてきて,ビルド用のプロジェクトを作ってリンクするのが楽.
あと,ついでに C++ 用のシンプルなラッパーを書いた.
追記: この記事を書いた後で,SQLiteCpp が存在していることに気づいた・・・ まあ,ちょっとSQLiteを操作したいだけで,依存ライブラリ増やしたくないって場合にはこの記事の内容が役に立つかもしれません.
手順
SQLite ビルド用のプロジェクトを sqlite
, SQLite を使いたいプロジェクトを app
とします.これらのプロジェクトは同じソリューションの中にあるものとします.
- 公式ページからソースコードをダウンロード
- Windows 向けのバイナリも配布されているが,DLLを使うための設定をする手間を考えるとあんまり変わらない
sqlite3.c
,sqlite3.h
,sqlite3ext.h
をsqlite
に追加sqlite
の構成の種類を.exe
から.lib
に変更するsqlite
のコンパイルオプションに/sdl-
を付加- これをつけないとエラーになってコンパイルできない
app
のインクルードディレクトリにsqlite
のディレクトリを追加app
の参照プロジェクトにsqlite
を追加
C++用のラッパー
これで,app
の中で SQLite の C/C++ APIを呼び出せるようになるのですが,そのままだと,エラーチェックやcloseの処理が面倒なので,簡単なラッパーを書きました.SQL 直書きしかできないけど,簡単な用途ならこれで十分でしょう.ただし,フォーマットライブラリ(tinyformatなど)はあったほうが楽です.
※当然ですが,SQLインジェクション対策とかは何もやってないので,Webサービスとかで使うのはやめましょう.
/*
Simple wrapper for SQLite3
This code is Public Domain
**CAUTION**
1. No Warrantry
2. This code is *NOT* safe against SQL Injection.
3. This code can *NOT* handle BLOB or TEXT including '\0'.
4. You should *NOT* use this for production.
*/
// ============== ここから ヘッダー ===========
#include <cassert>
#include <iostream>
#include <vector>
#include <string>
#include <map>
#include <memory>
#include <stdexcept>
#include "sqlite3.h"
struct SQLite3 {
typedef std::shared_ptr<std::string> Value;
typedef std::vector<std::pair<std::string, Value>> Row;
sqlite3 *db;
SQLite3(const std::string &dbName);
~SQLite3();
std::vector<Row> exec(const std::string &sql);
};
// ============== ここから ライブラリ ===========
SQLite3::SQLite3(const std::string &dbName) : db(nullptr) {
int err = sqlite3_open(dbName.c_str(), &db);
if (err != SQLITE_OK) {
std::string errstr = sqlite3_errmsg(db);
sqlite3_close_v2(db);
db = nullptr;
throw std::runtime_error(errstr);
}
}
SQLite3::~SQLite3() {
sqlite3_close_v2(db);
db = nullptr;
}
std::vector<SQLite3::Row> SQLite3::exec(const std::string &sql) {
char *errmsg = nullptr;
auto callback = [](void *result_, int argc,
char **argv, char **colNames) {
std::vector<Row> *ret = reinterpret_cast<std::vector<Row> *>(result_);
ret->push_back(Row());
for (int i = 0; i < argc; i++) {
if (argv[i] != nullptr) {
ret->back()
.push_back(std::make_pair(colNames[i],
Value(new std::string(argv[i]))));
} else {
ret->back()
.push_back(std::make_pair(colNames[i], Value(nullptr)));
}
}
return SQLITE_OK;
};
std::vector<Row> ret;
int err = sqlite3_exec(db, sql.c_str(), callback, &ret, &errmsg);
if (err != SQLITE_OK) {
std::string errstr = errmsg;
sqlite3_free(errmsg);
errmsg = nullptr;
throw std::runtime_error(errstr);
}
return ret;
}
// ============== ここから テスト ===========
int main() {
std::cout << "=== Testing ===" << std::endl;
SQLite3 db(":memory:");
std::vector<SQLite3::Row> ret;
db.exec("CREATE TABLE test ( "
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"name TEXT, "
"price INTEGER, "
"weight REAL, "
"createdAt DATE DEFAULT CURRENT_TIMESTAMP);");
db.exec("INSERT INTO test (name, price, weight) VALUES "
"('apple', 100, 310.5);");
db.exec("INSERT INTO test (name, price, weight) VALUES "
"('banana', 200, 135.8);");
db.exec("INSERT INTO test (name, price, weight) VALUES "
"('orange', 130, 222.2);");
ret = db.exec("SELECT * FROM test "
"WHERE 100 <= price AND price <= 150 "
"ORDER BY id DESC");
std::cout << "===" << std::endl;
for (const auto &row : ret) {
// use as map
std::map<std::string, SQLite3::Value> rowMap(row.begin(), row.end());
std::cout << "[" << *rowMap["name"] << "]" << std::endl;
// iteration
for (const auto &col : row) {
std::cout << col.first << ':' << *col.second << std::endl;
}
std::cout << "===" << std::endl;
}
// note: colunm name may be duplicate
ret = db.exec("SELECT * FROM test test1 CROSS JOIN test test2 "
"WHERE 100 <= test1.price AND test1.price <= 150 "
"ORDER BY test1.id DESC");
for (const auto &row : ret) {
for (const auto &col : row) {
std::cout << col.first << ':' << *col.second << std::endl;
}
std::cout << "===" << std::endl;
}
}