SQLite3 (c++) の謎動作に悩まされる

SQLite3 の C インタフェースを使っていた時の話です。

以下のようなテーブルを定義したとします。

CREATE TABLE example (
  id integer primary key autoincrements,
  name varchar(1024) not null unique
);

関数 main の第1引数に文字列リストを渡し、それを example テーブルに登録していくプログラムを書きます。

当初は以下のようなプログラムを書いていました。

#include <iostream>
#include <fstream>
#include <string>
#include <sqlite3.h>
#pragma comment(lib, "sqlite3.dll")

class MyException {
  int errcode;
  std::wstring reason;
  MyException(int errcode, const std::wstring &reason) : reason(reason) {}
  public void write() {
    std::wcout << this->reason << L"(" << this->errcode << L")" << std::wendl;
  }
};

void trim(std::wstring &s) {
  auto pos = s.find_first_not_of(L" \t\r\n");
  if (pos != std::wstring::npos) {
    s = s.substr(pos + 1);
  }
  pos = s.find_last_not_of(L" \t\r\n");
  if (pos != std::wstring::npos) {
    s = s.substr(0, pos);
  }
}

int main(int argc, wchar_t **argv) {
  int err;
  sqlite3 *db;
  sqlite3_stmt *stmt;

  std::locatle("Japanese");

  if (argc < 2) {
    std::wcout << "Usage: example <string list file>" << std::wendl;
    return 1;
  }
  err = sqlite3_open16("mydatabase.db", &db);
  if (err != SQLITE_OK) {
    std::wcout << "Error:" << err << std::wendl;
    return 1;
  }

  try {
    std::wifstream ifs(argv[1]);
    err = sqlite3_exec(db, "BEGIN TRANSACTION", -1, nullptr, nullptr, nullptr);
    if (err != SQLITE_OK) {
      throw MyException(err, "Begin Transaction");
    }
    std::wstring line;
    while (getline(ifs, line)) {
      trim(line);
      err = sqlite3_prepare16_v2(db, L"select id from example where name=?", -1, &stmt, nullptr);
      if (err != SQLITE_OK) {
        throw MyException(err, "select");
      }
      sqlite3_bind_text16(stmt, 1, line.data(), line.length(), nullptr);
      int id = 0;
      while (sqlite3_step(stmt) == SQLITE_ROW) {
        id = sqlite3_column_int(stmt, 0);
      }
      slite3_finalize(stmt);

      if (id == 0) {
        err = sqlite3_prepare16_v2(db, L"insert into example (name) values (?)", -1, &stmt, nullptr);
        if (err != SQLITE_OK) {
          throw MyException(err, "insert");
        }
        sqlite3_bind_text16(stmt, 1, line.data(), line.length(), nullptr);
        sqlite3_step(stmt);
        id = (int)sqlite3_last_insert_rowid(db);
        std::wcout << L"New item " << line << L"(" << id << L")" << std::weol;
        sqlite3_finalize(stmt);
      }
    }  
    sqlite3_exec(db, "COMMIT", -1, nullptr, nullptr, nullptr);
  } catch (MyException e) {
    e.write();
    sqlite3_exec(db, "ROLLBACK", -1, nullptr, nullptr, nullptr);
  }
  sqlite3_close(db);
  return 0;
}

しかし、これだと謎動作が。期待した文字列「ではない」文字列にマッチして id 値が出鱈目になってしまいます。

SQLITE3 のマニュアルにはちゃんと書いてありませんが、どうやら sqlite3_bind_text16() の第4引数はバイト数を与えないといけないようです。

sqlite3_bind_text16(stmt, 1, line.data(), line.size() * sizeof(wchar_t), nullptr);

ぐぐっても第4パラメータを -1 としている記事しかなかったので、書いてみました。

投稿者について
みのしす

小さいときは科学者になろうとしたのに、その時にたまたま身に着けたプログラミングで未だに飯を食っているしがないおじさんです。(年齢的にはもうすぐおじいさん)

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です