mitaki28.info blog

Visual C++ で SQLite を使う

まとめ

意外と情報が少なくて苦労した.結論としては,SQLite 公式のソースコードを落としてきて,ビルド用のプロジェクトを作ってリンクするのが楽.

あと,ついでに C++ 用のシンプルなラッパーを書いた.

追記: この記事を書いた後で,SQLiteCpp が存在していることに気づいた・・・ まあ,ちょっとSQLiteを操作したいだけで,依存ライブラリ増やしたくないって場合にはこの記事の内容が役に立つかもしれません.

手順

SQLite ビルド用のプロジェクトを sqlite, SQLite を使いたいプロジェクトを app とします.これらのプロジェクトは同じソリューションの中にあるものとします.

  1. 公式ページからソースコードをダウンロード
    • Windows 向けのバイナリも配布されているが,DLLを使うための設定をする手間を考えるとあんまり変わらない
  2. sqlite3.c, sqlite3.h, sqlite3ext.hsqlite に追加
  3. sqlite の構成の種類を .exe から .lib に変更する
  4. sqlite のコンパイルオプションに /sdl- を付加
    • これをつけないとエラーになってコンパイルできない
  5. app のインクルードディレクトリに sqlite のディレクトリを追加
  6. 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;
  }  
}

一応,gist でも公開しています